diff --git a/.backportrc.json b/.backportrc.json index db7ad3b0eb887..94c2549418f17 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "repoName": "kibana", "targetBranchChoices": [ "main", + "8.5", "8.4", "8.3", "8.2", @@ -41,7 +42,7 @@ "backport" ], "branchLabelMapping": { - "^v8.5.0$": "main", + "^v8.6.0$": "main", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 3fd459ffc64c1..ebaeb6c3692b7 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -56,7 +56,7 @@ disabled: - x-pack/test/fleet_packages/config.ts # Scalability testing config that we run in its own pipeline - - x-pack/test/performance/scalability/config.ts + - x-pack/test/scalability/config.ts defaultQueue: 'n2-4-spot' enabled: @@ -267,10 +267,10 @@ enabled: - x-pack/test/ui_capabilities/spaces_only/config.ts - x-pack/test/upgrade_assistant_integration/config.js - x-pack/test/usage_collection/config.ts - - x-pack/test/performance/journeys/ecommerce_dashboard/config.ts - - x-pack/test/performance/journeys/flight_dashboard/config.ts - - x-pack/test/performance/journeys/login/config.ts - - x-pack/test/performance/journeys/many_fields_discover/config.ts - - x-pack/test/performance/journeys/promotion_tracking_dashboard/config.ts - - x-pack/test/performance/journeys/web_logs_dashboard/config.ts - - x-pack/test/performance/journeys/data_stress_test_lens/config.ts + - x-pack/performance/journeys/ecommerce_dashboard.ts + - x-pack/performance/journeys/flight_dashboard.ts + - x-pack/performance/journeys/login.ts + - x-pack/performance/journeys/many_fields_discover.ts + - x-pack/performance/journeys/promotion_tracking_dashboard.ts + - x-pack/performance/journeys/web_logs_dashboard.ts + - x-pack/performance/journeys/data_stress_test_lens.ts diff --git a/.buildkite/pipelines/artifacts.yml b/.buildkite/pipelines/artifacts.yml index b6d3cc9fc9b14..e133c4b569317 100644 --- a/.buildkite/pipelines/artifacts.yml +++ b/.buildkite/pipelines/artifacts.yml @@ -75,6 +75,7 @@ steps: label: 'Cloud Deployment' soft_fail: - exit_status: 255 + - exit_status: -1 agents: queue: n2-2 timeout_in_minutes: 30 diff --git a/.buildkite/pipelines/performance/daily.yml b/.buildkite/pipelines/performance/daily.yml index 3e0a813adce49..10f137a5c5088 100644 --- a/.buildkite/pipelines/performance/daily.yml +++ b/.buildkite/pipelines/performance/daily.yml @@ -1,19 +1,19 @@ steps: - - label: ':male-mechanic::skin-tone-2: Pre-Build' + - label: '👨‍🔧 Pre-Build' command: .buildkite/scripts/lifecycle/pre_build.sh agents: queue: kibana-default - wait - - label: ':factory_worker: Build Kibana Distribution and Plugins' + - label: '🧑‍🏭 Build Kibana Distribution and Plugins' command: .buildkite/scripts/steps/build_kibana.sh agents: queue: c2-16 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" - - label: ':muscle: Performance Tests with Playwright config' + - label: '💪 Performance Tests with Playwright config' command: .buildkite/scripts/steps/functional/performance_playwright.sh agents: queue: kb-static-ubuntu @@ -21,13 +21,13 @@ steps: key: tests timeout_in_minutes: 60 - - label: ':shipit: Performance Tests dataset extraction for scalability benchmarking' + - label: '🚢 Performance Tests dataset extraction for scalability benchmarking' command: .buildkite/scripts/steps/functional/scalability_dataset_extraction.sh agents: queue: n2-2 depends_on: tests - - label: ':chart_with_upwards_trend: Report performance metrics to ci-stats' + - label: '📈 Report performance metrics to ci-stats' command: .buildkite/scripts/steps/functional/report_performance_metrics.sh agents: queue: n2-2 @@ -36,7 +36,7 @@ steps: - wait: ~ continue_on_failure: true - - label: ':male_superhero::skin-tone-2: Post-Build' + - label: '🦸 Post-Build' command: .buildkite/scripts/lifecycle/post_build.sh agents: queue: kibana-default diff --git a/.buildkite/pull_requests.json b/.buildkite/pull_requests.json index 1c6e1ae3ce7bc..027c2de8bf915 100644 --- a/.buildkite/pull_requests.json +++ b/.buildkite/pull_requests.json @@ -42,7 +42,12 @@ "kibana_versions_check": true, "kibana_build_reuse": true, "kibana_build_reuse_pipeline_slugs": ["kibana-pull-request", "kibana-on-merge"], - "kibana_build_reuse_regexes": ["^test/", "^x-pack/test/"] + "kibana_build_reuse_regexes": [ + "^test/", + "^x-pack/test/", + "/__snapshots__/", + "\\.test\\.(ts|tsx|js|jsx)" + ] } ] } diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index 59cac2eaf5259..617a80524e6dd 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -90,7 +90,7 @@ const uploadPipeline = (pipelineContent: string | object) => { } if ( - (await doAnyChangesMatch([/^x-pack\/plugins\/apm/])) || + (await doAnyChangesMatch([/^x-pack\/plugins\/apm/, /^packages\/kbn-apm-synthtrace/])) || GITHUB_PR_LABELS.includes('ci:all-cypress-suites') ) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/apm_cypress.yml')); diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index adab313e4c382..cdf2e449f7a6b 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -4,16 +4,33 @@ set -euo pipefail source .buildkite/scripts/common/util.sh +is_test_execution_step + .buildkite/scripts/bootstrap.sh # These tests are running on static workers so we have to make sure we delete previous build of Kibana rm -rf "$KIBANA_BUILD_LOCATION" .buildkite/scripts/download_build_artifacts.sh -echo --- Run Performance Tests with Playwright config +echo "--- 🦺 Starting Elasticsearch" node scripts/es snapshot& +export esPid=$! +trap 'kill ${esPid}' EXIT -esPid=$! +export TEST_ES_URL=http://elastic:changeme@localhost:9200 +export TEST_ES_DISABLE_STARTUP=true + +# Pings the es server every second for up to 2 minutes until it is green +curl \ + --fail \ + --silent \ + --retry 120 \ + --retry-delay 1 \ + --retry-connrefused \ + -XGET "${TEST_ES_URL}/_cluster/health?wait_for_nodes=>=1&wait_for_status=yellow" \ + > /dev/null + +echo "✅ ES is ready and will continue to run in the background" # unset env vars defined in other parts of CI for automatic APM collection of # Kibana. We manage APM config in our FTR config and performance service, and @@ -29,39 +46,27 @@ unset ELASTIC_APM_SERVER_URL unset ELASTIC_APM_SECRET_TOKEN unset ELASTIC_APM_GLOBAL_LABELS - -export TEST_ES_URL=http://elastic:changeme@localhost:9200 -export TEST_ES_DISABLE_STARTUP=true - -# Pings the es server every seconds 2 mins until it is status is green -curl --retry 120 \ - --retry-delay 1 \ - --retry-all-errors \ - -I -XGET "${TEST_ES_URL}/_cluster/health?wait_for_nodes=>=1&wait_for_status=yellow" - -journeys=("login" "ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard" "many_fields_discover" "data_stress_test_lens") - -for i in "${journeys[@]}"; do - echo "JOURNEY[${i}] is running" - - export TEST_PERFORMANCE_PHASE=WARMUP - export JOURNEY_NAME="${i}" - - checks-reporter-with-killswitch "Run Performance Tests with Playwright Config (Journey:${i},Phase: WARMUP)" \ - node scripts/functional_tests \ - --config "x-pack/test/performance/journeys/${i}/config.ts" \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --debug \ - --bail - - export TEST_PERFORMANCE_PHASE=TEST - - checks-reporter-with-killswitch "Run Performance Tests with Playwright Config (Journey:${i},Phase: TEST)" \ - node scripts/functional_tests \ - --config "x-pack/test/performance/journeys/${i}/config.ts" \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --debug \ - --bail +for journey in x-pack/performance/journeys/*; do + set +e + + phases=("WARMUP" "TEST") + for phase in "${phases[@]}"; do + echo "--- $journey - $phase" + + export TEST_PERFORMANCE_PHASE="$phase" + node scripts/functional_tests \ + --config "$journey" \ + --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ + --debug \ + --bail + + status=$? + if [ $status -ne 0 ]; then + echo "^^^ +++" + echo "❌ FTR failed with status code: $status" + exit 1 + fi + done + + set -e done - -kill "$esPid" diff --git a/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh b/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh index a8711c8d2f58a..a2b81f538b92b 100755 --- a/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh +++ b/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh @@ -15,19 +15,16 @@ OUTPUT_DIR="${KIBANA_DIR}/${OUTPUT_REL}" .buildkite/scripts/bootstrap.sh echo "--- Extract APM metrics" -scalabilityJourneys=("login" "ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard" "many_fields_discover") - -for i in "${scalabilityJourneys[@]}"; do - JOURNEY_NAME="${i}" - echo "Looking for JOURNEY=${JOURNEY_NAME} and BUILD_ID=${BUILD_ID} in APM traces" - - node scripts/extract_performance_testing_dataset \ - --config "x-pack/test/performance/journeys/${i}/config.ts" \ - --buildId "${BUILD_ID}" \ - --es-url "${ES_SERVER_URL}" \ - --es-username "${USER_FROM_VAULT}" \ - --es-password "${PASS_FROM_VAULT}" \ - --without-static-resources +for journey in x-pack/performance/journeys/*; do + echo "Looking for journey=${journey} and BUILD_ID=${BUILD_ID} in APM traces" + + node scripts/extract_performance_testing_dataset \ + --config "${journey}" \ + --buildId "${BUILD_ID}" \ + --es-url "${ES_SERVER_URL}" \ + --es-username "${USER_FROM_VAULT}" \ + --es-password "${PASS_FROM_VAULT}" \ + --without-static-resources done echo "--- Creating scalability dataset in ${OUTPUT_REL}" diff --git a/.buildkite/scripts/steps/scalability/benchmarking.sh b/.buildkite/scripts/steps/scalability/benchmarking.sh index 3bd762796e189..00d530b7a44af 100755 --- a/.buildkite/scripts/steps/scalability/benchmarking.sh +++ b/.buildkite/scripts/steps/scalability/benchmarking.sh @@ -57,7 +57,10 @@ checkout_and_compile_load_runner() { upload_test_results() { cd "${KIBANA_DIR}" - echo "--- Archive Gatling reports and upload as build artifacts" + echo "Upload server logs as build artifacts" + tar -czf server-logs.tar.gz data/ftr_servers_logs/**/* + buildkite-agent artifact upload server-logs.tar.gz + echo "--- Upload Gatling reports as build artifacts" tar -czf "scalability_test_report.tar.gz" --exclude=simulation.log -C kibana-load-testing/target gatling buildkite-agent artifact upload "scalability_test_report.tar.gz" cd "${LATEST_RUN_ARTIFACTS_DIR}" @@ -115,6 +118,7 @@ for journey in scalability_traces/server/*; do node scripts/functional_tests \ --config x-pack/test/performance/scalability/config.ts \ --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ + --logToFile \ --debug done diff --git a/.eslintrc.js b/.eslintrc.js index c36e8b5e7e668..df107348cfafc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -178,6 +178,7 @@ const DEV_PATTERNS = [ 'x-pack/{dev-tools,tasks,scripts,test,build_chromium}/**/*', 'x-pack/plugins/*/server/scripts/**/*', 'x-pack/plugins/fleet/cypress', + 'x-pack/performance/**/*', ]; /** Restricted imports with suggested alternatives */ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fba77bf614d5d..dc7a6270638c8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -265,6 +265,7 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/licensing/ @elastic/kibana-core /x-pack/plugins/global_search/ @elastic/kibana-core /x-pack/plugins/cloud/ @elastic/kibana-core +/x-pack/plugins/cloud_integrations/ @elastic/kibana-core /x-pack/plugins/saved_objects_tagging/ @elastic/kibana-core /x-pack/test/saved_objects_field_count/ @elastic/kibana-core /x-pack/test/saved_object_tagging/ @elastic/kibana-core @@ -417,6 +418,7 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/security_solution/common/search_strategy/security_solution/user @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/e2e/cases @elastic/security-threat-hunting-explore +/x-pack/plugins/security_solution/cypress/e2e/filters @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/e2e/host_details @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/e2e/hosts @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/e2e/network @elastic/security-threat-hunting-explore @@ -859,9 +861,12 @@ packages/kbn-eslint-plugin-disable @elastic/kibana-operations packages/kbn-eslint-plugin-eslint @elastic/kibana-operations packages/kbn-eslint-plugin-imports @elastic/kibana-operations packages/kbn-expect @elastic/kibana-operations +packages/kbn-failed-test-reporter-cli @elastic/kibana-operations packages/kbn-field-types @elastic/kibana-app-services packages/kbn-find-used-node-modules @elastic/kibana-operations packages/kbn-flot-charts @elastic/kibana-operations +packages/kbn-ftr-common-functional-services @elastic/kibana-operations +packages/kbn-ftr-screenshot-filename @elastic/kibana-operations packages/kbn-generate @elastic/kibana-operations packages/kbn-get-repo-files @elastic/kibana-operations packages/kbn-handlebars @elastic/kibana-security @@ -872,6 +877,7 @@ packages/kbn-import-resolver @elastic/kibana-operations packages/kbn-interpreter @elastic/kibana-app-services packages/kbn-io-ts-utils @elastic/apm-ui packages/kbn-jest-serializers @elastic/kibana-operations +packages/kbn-journeys @elastic/kibana-operations packages/kbn-kibana-manifest-schema @elastic/kibana-operations packages/kbn-logging @elastic/kibana-core packages/kbn-logging-mocks @elastic/kibana-core @@ -936,6 +942,7 @@ packages/kbn-utility-types-jest @elastic/kibana-operations packages/kbn-utils @elastic/kibana-operations packages/kbn-yarn-lock-validator @elastic/kibana-operations packages/shared-ux/avatar/solution @elastic/shared-ux +packages/shared-ux/avatar/user_profile/impl @elastic/shared-ux packages/shared-ux/button_toolbar @elastic/shared-ux packages/shared-ux/button/exit_full_screen/impl @elastic/shared-ux packages/shared-ux/button/exit_full_screen/mocks @elastic/shared-ux @@ -965,6 +972,9 @@ packages/shared-ux/page/solution_nav @elastic/shared-ux packages/shared-ux/prompt/no_data_views/impl @elastic/shared-ux packages/shared-ux/prompt/no_data_views/mocks @elastic/shared-ux packages/shared-ux/prompt/no_data_views/types @elastic/shared-ux +packages/shared-ux/router/impl @elastic/shared-ux +packages/shared-ux/router/mocks @elastic/shared-ux +packages/shared-ux/router/types @elastic/shared-ux packages/shared-ux/storybook/config @elastic/shared-ux packages/shared-ux/storybook/mock @elastic/shared-ux x-pack/packages/ml/agg_utils @elastic/ml-ui diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index dcbf086f39208..880dc0b122f6a 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: 2022-09-20 +date: 2022-09-22 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 99d010dcd47bb..69f9463fb9e2e 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index fb6b6937fc76b..fd4f21939b6e8 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 70b1838415a1c..da826f60ffc9c 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index b0e9d5757bffd..42d291bc57f02 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -781,7 +781,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services/{serviceName}/serviceNodes\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_duration\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_count\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services/{serviceName}/serviceNodes\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service_groups/services_count\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_duration\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_count\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\"" ], "path": "x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -3881,6 +3881,26 @@ }, ", { terms: string[]; }, ", "APMRouteCreateOptions", + ">; \"GET /internal/apm/service_groups/services_count\": ", + "ServerRoute", + "<\"GET /internal/apm/service_groups/services_count\", ", + "TypeC", + "<{ query: ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { servicesCounts: Record; }, ", + "APMRouteCreateOptions", ">; \"GET /internal/apm/service-group/services\": ", "ServerRoute", "<\"GET /internal/apm/service-group/services\", ", @@ -3932,18 +3952,14 @@ "<\"POST /internal/apm/service-group\", ", "TypeC", "<{ query: ", - "IntersectionC", + "UnionC", "<[", - "TypeC", - "<{ start: ", - "Type", - "; end: ", - "Type", - "; }>, ", "PartialC", "<{ serviceGroupId: ", "StringC", - "; }>]>; body: ", + "; }>, ", + "UndefinedC", + "]>; body: ", "TypeC", "<{ groupName: ", "StringC", @@ -5091,11 +5107,9 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { charts: { title: string; key: string; yUnit: ", - "YUnit", - "; series: { title: string; key: string; type: ", - "ChartType", - "; color: string; overallValue: number; data: { x: number; y: number | null; }[]; }[]; }[]; }, ", + ", { charts: ", + "FetchAndTransformMetrics", + "[]; }, ", "APMRouteCreateOptions", ">; \"POST /internal/apm/latency/overall_distribution/transactions\": ", "ServerRoute", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 4e665c7bfea03..bbd29cd9d6bdf 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; @@ -21,7 +21,7 @@ Contact [APM UI](https://github.com/orgs/elastic/teams/apm-ui) for questions reg | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 38 | 0 | 38 | 52 | +| 38 | 0 | 38 | 51 | ## Client diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index dcb08b6198153..7f66666be6ed8 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: 2022-09-20 +date: 2022-09-22 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 dce7e1d14c5ca..2667ba38a0f73 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: 2022-09-20 +date: 2022-09-22 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 46a35412e6ae1..4661b73c4cef5 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: 2022-09-20 +date: 2022-09-22 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 a6ee546714ecf..2601c456b45e1 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: 2022-09-20 +date: 2022-09-22 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 b62a72791c792..806f99fc1dd39 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: 2022-09-20 +date: 2022-09-22 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 d88f07961f1e9..0a73a5a20eec9 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_experiments.devdocs.json b/api_docs/cloud_experiments.devdocs.json new file mode 100644 index 0000000000000..0f827e35ec52a --- /dev/null +++ b/api_docs/cloud_experiments.devdocs.json @@ -0,0 +1,370 @@ +{ + "id": "cloudExperiments", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsMetric", + "type": "Interface", + "tags": [], + "label": "CloudExperimentsMetric", + "description": [ + "\nDefinition of the metric to report back to the A/B testing service to measure the conversions.\n" + ], + "signature": [ + { + "pluginId": "cloudExperiments", + "scope": "common", + "docId": "kibCloudExperimentsPluginApi", + "section": "def-common.CloudExperimentsMetric", + "text": "CloudExperimentsMetric" + }, + "" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsMetric.name", + "type": "Uncategorized", + "tags": [], + "label": "name", + "description": [ + "\nThe name of the metric {@link CloudExperimentsMetricNames}" + ], + "signature": [ + "never" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsMetric.meta", + "type": "Uncategorized", + "tags": [], + "label": "meta", + "description": [ + "\nAny optional data to enrich the context of the metric. Or if the conversion is based on a non-numeric value." + ], + "signature": [ + "Data | undefined" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsMetric.value", + "type": "number", + "tags": [], + "label": "value", + "description": [ + "\nThe numeric value of the metric. Bear in mind that they are averaged by the underlying solution.\nTypical values to report here are time-to-action, number of panels in a loaded dashboard, and page load time." + ], + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginSetup", + "type": "Interface", + "tags": [], + "label": "CloudExperimentsPluginSetup", + "description": [ + "\nThe contract of the setup lifecycle method.\n" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginSetup.identifyUser", + "type": "Function", + "tags": [ + "deprecated" + ], + "label": "identifyUser", + "description": [ + "\nIdentifies the user in the A/B testing service.\nFor now, we only rely on the user ID. In the future, we may request further details for more targeted experiments." + ], + "signature": [ + "(userId: string, userMetadata?: Record | undefined) => void" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": true, + "trackAdoption": false, + "references": [ + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/public/plugin.tsx" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/server/plugin.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/public/plugin.test.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/public/plugin.test.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/public/plugin.test.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/public/plugin.test.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/server/plugin.test.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/server/plugin.test.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/server/plugin.test.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/server/plugin.test.ts" + } + ], + "children": [ + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginSetup.identifyUser.$1", + "type": "string", + "tags": [], + "label": "userId", + "description": [ + "The unique identifier of the user in the experiment." + ], + "signature": [ + "string" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginSetup.identifyUser.$2", + "type": "Object", + "tags": [], + "label": "userMetadata", + "description": [ + "Additional attributes to the user. Take care to ensure these values do not contain PII." + ], + "signature": [ + "Record | undefined" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginStart", + "type": "Interface", + "tags": [], + "label": "CloudExperimentsPluginStart", + "description": [ + "\nThe contract of the start lifecycle method\n" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginStart.getVariation", + "type": "Function", + "tags": [], + "label": "getVariation", + "description": [ + "\nFetch the configuration assigned to variation `configKey`. If nothing is found, fallback to `defaultValue`." + ], + "signature": [ + "(featureFlagName: \"security-solutions.add-integrations-url\", defaultValue: Data) => Promise" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginStart.getVariation.$1", + "type": "string", + "tags": [], + "label": "featureFlagName", + "description": [ + "The name of the key to find the config variation. {@link CloudExperimentsFeatureFlagNames }." + ], + "signature": [ + "\"security-solutions.add-integrations-url\"" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginStart.getVariation.$2", + "type": "Uncategorized", + "tags": [], + "label": "defaultValue", + "description": [ + "The fallback value in case no variation is found." + ], + "signature": [ + "Data" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginStart.reportMetric", + "type": "Function", + "tags": [], + "label": "reportMetric", + "description": [ + "\nReport metrics back to the A/B testing service to measure the conversion rate for each variation in the experiment." + ], + "signature": [ + "(metric: ", + { + "pluginId": "cloudExperiments", + "scope": "common", + "docId": "kibCloudExperimentsPluginApi", + "section": "def-common.CloudExperimentsMetric", + "text": "CloudExperimentsMetric" + }, + ") => void" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsPluginStart.reportMetric.$1", + "type": "Object", + "tags": [], + "label": "metric", + "description": [ + "{@link CloudExperimentsMetric }" + ], + "signature": [ + { + "pluginId": "cloudExperiments", + "scope": "common", + "docId": "kibCloudExperimentsPluginApi", + "section": "def-common.CloudExperimentsMetric", + "text": "CloudExperimentsMetric" + }, + "" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsFeatureFlagNames", + "type": "Type", + "tags": [], + "label": "CloudExperimentsFeatureFlagNames", + "description": [ + "\nThe names of the feature flags declared in Kibana.\nValid keys are defined in {@link FEATURE_FLAG_NAMES}. When using a new feature flag, add the name to the list.\n" + ], + "signature": [ + "\"security-solutions.add-integrations-url\"" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "cloudExperiments", + "id": "def-common.CloudExperimentsMetricNames", + "type": "Type", + "tags": [], + "label": "CloudExperimentsMetricNames", + "description": [ + "\nThe names of the metrics declared in Kibana.\nValid keys are defined in {@link METRIC_NAMES}. When reporting a new metric, add the name to the list.\n" + ], + "signature": [ + "never" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx new file mode 100644 index 0000000000000..8cb4e1537763e --- /dev/null +++ b/api_docs/cloud_experiments.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: kibCloudExperimentsPluginApi +slug: /kibana-dev-docs/api/cloudExperiments +title: "cloudExperiments" +image: https://source.unsplash.com/400x175/?github +description: API docs for the cloudExperiments plugin +date: 2022-09-22 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] +--- +import cloudExperimentsObj from './cloud_experiments.devdocs.json'; + +Provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments. + +Contact [Kibana Core](https://github.com/orgs/elastic/teams/@elastic/kibana-core) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 16 | 0 | 0 | 0 | + +## Common + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index beeef9814456f..e2848bff600e0 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: 2022-09-20 +date: 2022-09-22 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 266884e1d4607..b99308ac7643d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 6e0b9d0ebebfd..4427f251a9ef3 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index dd0a8f399f350..ce4999200ef16 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -11219,6 +11219,14 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/saved_objects.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/saved_objects.ts" + }, { "plugin": "savedObjects", "path": "src/plugins/saved_objects/public/saved_object/saved_object.test.ts" @@ -11899,6 +11907,69 @@ ], "returnComment": [] }, + { + "parentPluginId": "core", + "id": "def-public.SavedObjectsClientContract.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\nDeletes multiple documents at once" + ], + "signature": [ + "(objects: ", + "SavedObjectTypeIdTuple", + "[], options?: ", + "SavedObjectsBulkDeleteOptions", + " | undefined) => Promise<", + "SavedObjectsBulkDeleteResponse", + ">" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-public.SavedObjectsClientContract.bulkDelete.$1", + "type": "Array", + "tags": [], + "label": "objects", + "description": [ + "- an array of objects containing id, type" + ], + "signature": [ + "SavedObjectTypeIdTuple", + "[]" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "core", + "id": "def-public.SavedObjectsClientContract.bulkDelete.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [ + "- optional force argument to force deletion of objects in a namespace other than the scoped client" + ], + "signature": [ + "SavedObjectsBulkDeleteOptions", + " | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [ + "The bulk delete result for the saved objects for the given types and ids." + ] + }, { "parentPluginId": "core", "id": "def-public.SavedObjectsClientContract.find", @@ -15423,7 +15494,7 @@ "signature": [ "Pick<", "Toast", - ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\"> & { title?: string | ", + ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\" | \"css\"> & { title?: string | ", "MountPoint", " | undefined; text?: string | ", "MountPoint", @@ -15464,7 +15535,7 @@ "signature": [ "Pick<", "Toast", - ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\"> & { title?: string | ", + ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\" | \"css\"> & { title?: string | ", "MountPoint", " | undefined; text?: string | ", "MountPoint", @@ -17831,6 +17902,63 @@ ], "returnComment": [] }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRepository.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\n {@inheritDoc ISavedObjectsRepository.bulkDelete}" + ], + "signature": [ + "(objects: ", + "SavedObjectsBulkDeleteObject", + "[], options?: ", + "SavedObjectsBulkDeleteOptions", + " | undefined) => Promise<", + "SavedObjectsBulkDeleteResponse", + ">" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server-internal/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRepository.bulkDelete.$1", + "type": "Array", + "tags": [], + "label": "objects", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteObject", + "[]" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server-internal/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRepository.bulkDelete.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteOptions", + " | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server-internal/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, { "parentPluginId": "core", "id": "def-server.SavedObjectsRepository.deleteByNamespace", @@ -22645,6 +22773,104 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.total", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.total'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "node_modules/@types/kbn__core-usage-data-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.default.total", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.default.total'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "node_modules/@types/kbn__core-usage-data-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "node_modules/@types/kbn__core-usage-data-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "node_modules/@types/kbn__core-usage-data-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.custom.total", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.custom.total'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "node_modules/@types/kbn__core-usage-data-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "node_modules/@types/kbn__core-usage-data-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "node_modules/@types/kbn__core-usage-data-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "core", "id": "def-server.CoreUsageStats.apiCalls.savedObjectsCreate.total", @@ -30442,6 +30668,67 @@ ], "returnComment": [] }, + { + "parentPluginId": "core", + "id": "def-server.ISavedObjectsRepository.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\nDeletes multiple documents at once" + ], + "signature": [ + "(objects: ", + "SavedObjectsBulkDeleteObject", + "[], options?: ", + "SavedObjectsBulkDeleteOptions", + " | undefined) => Promise<", + "SavedObjectsBulkDeleteResponse", + ">" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.ISavedObjectsRepository.bulkDelete.$1", + "type": "Array", + "tags": [], + "label": "objects", + "description": [ + "- an array of objects containing id and type" + ], + "signature": [ + "SavedObjectsBulkDeleteObject", + "[]" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "core", + "id": "def-server.ISavedObjectsRepository.bulkDelete.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteOptions", + " | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [ + "- { statuses: [{ id, type, success, error: { message } }] }" + ] + }, { "parentPluginId": "core", "id": "def-server.ISavedObjectsRepository.deleteByNamespace", @@ -40565,6 +40852,14 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/saved_objects.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/saved_objects.ts" + }, { "plugin": "savedObjects", "path": "src/plugins/saved_objects/public/saved_object/saved_object.test.ts" @@ -41252,6 +41547,131 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsBulkDeleteObject", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteObject", + "description": [ + "\n" + ], + "signature": [ + "SavedObjectsBulkDeleteObject" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsBulkDeleteObject.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsBulkDeleteObject.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsBulkDeleteOptions", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteOptions", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteOptions", + " extends ", + "SavedObjectsBaseOptions" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsBulkDeleteOptions.refresh", + "type": "CompoundType", + "tags": [], + "label": "refresh", + "description": [ + "The Elasticsearch Refresh setting for this operation" + ], + "signature": [ + "MutatingOperationRefreshSetting", + " | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsBulkDeleteOptions.force", + "type": "CompoundType", + "tags": [], + "label": "force", + "description": [ + "\nForce deletion of all objects that exists in multiple namespaces, applied to all objects." + ], + "signature": [ + "boolean | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsBulkDeleteResponse", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteResponse", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteResponse" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsBulkDeleteResponse.statuses", + "type": "Array", + "tags": [], + "label": "statuses", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteStatus", + "[]" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.SavedObjectsBulkGetObject", @@ -41931,6 +42351,63 @@ ], "returnComment": [] }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsClientContract.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\nDeletes multiple SavedObjects batched together as a single request\n" + ], + "signature": [ + "(objects: ", + "SavedObjectsBulkDeleteObject", + "[], options?: ", + "SavedObjectsBulkDeleteOptions", + " | undefined) => Promise<", + "SavedObjectsBulkDeleteResponse", + ">" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsClientContract.bulkDelete.$1", + "type": "Array", + "tags": [], + "label": "objects", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteObject", + "[]" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsClientContract.bulkDelete.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteOptions", + " | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, { "parentPluginId": "core", "id": "def-server.SavedObjectsClientContract.find", diff --git a/api_docs/core.mdx b/api_docs/core.mdx index ccf426c9b6909..413a21a58e12e 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2657 | 0 | 45 | 2 | +| 2684 | 0 | 45 | 0 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 6407c53bc8db9..db608cbbc805b 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: 2022-09-20 +date: 2022-09-22 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 53da1adcb0d6e..8a21209e990fb 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: 2022-09-20 +date: 2022-09-22 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 d39b30e5de370..ef500bb63d28d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index f2850615238c6..d8e572383857c 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -11419,11 +11419,11 @@ }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" + "path": "x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" }, { "plugin": "stackAlerts", - "path": "x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx" + "path": "x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx" }, { "plugin": "stackAlerts", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 5c4d12a6cb3d5..3a8209067815f 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 09c1081a7ca5a..661eef503c692 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: 2022-09-20 +date: 2022-09-22 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 3c16e2d5fedae..4801005b8e955 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: 2022-09-20 +date: 2022-09-22 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 21f01d16fd2a4..bec2047cd26a0 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: 2022-09-20 +date: 2022-09-22 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 cbfd433f1a095..a07ba6097b88f 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: 2022-09-20 +date: 2022-09-22 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 b47e57d899dc9..4619af2e25aa8 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: 2022-09-20 +date: 2022-09-22 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 0ba596a1b00b6..9394b62cc1a9c 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -17409,7 +17409,7 @@ "signature": [ "Pick<", "Toast", - ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\"> & { title?: string | ", + ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\" | \"css\"> & { title?: string | ", "MountPoint", " | undefined; text?: string | ", "MountPoint", @@ -21334,7 +21334,7 @@ "signature": [ "Pick<", "Toast", - ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\"> & { title?: string | ", + ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\" | \"css\"> & { title?: string | ", "MountPoint", " | undefined; text?: string | ", "MountPoint", diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index cea8ef1557292..7a8fd54baad3a 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: 2022-09-20 +date: 2022-09-22 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 e415597a7fcb6..e7c4937c65f86 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 2ca8394737f06..c834b151512de 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -43,7 +43,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement | - | -| | lens, observability, dataVisualizer, fleet, cloudSecurityPosture, discoverEnhanced, osquery, synthetics | - | +| | observability, dataVisualizer, fleet, cloudSecurityPosture, discoverEnhanced, osquery, synthetics | - | | | dataViewManagement, dataViews | - | | | dataViews, dataViewManagement | - | | | dataViewManagement, dataViews | - | @@ -62,13 +62,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | canvas | - | | | canvas | - | | | canvas | - | +| | cloud | - | | | spaces, savedObjectsManagement | - | | | spaces, savedObjectsManagement | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | | | enterpriseSearch | - | | | console, @kbn/core-elasticsearch-server-internal | - | | | spaces, security, alerting | 8.8.0 | -| | spaces, security, actions, alerting, ml, remoteClusters, graph, indexLifecycleManagement, mapsEms, painlessLab, rollup, searchprofiler, snapshotRestore, transform, upgradeAssistant | 8.8.0 | +| | spaces, security, actions, alerting, ml, remoteClusters, graph, indexLifecycleManagement, mapsEms, painlessLab, rollup, searchprofiler, securitySolution, snapshotRestore, transform, upgradeAssistant | 8.8.0 | | | embeddable, discover, presentationUtil, dashboard, graph | 8.8.0 | | | apm, security, securitySolution | 8.8.0 | | | apm, security, securitySolution | 8.8.0 | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 2d9fa24ea5276..5b36d18efade8 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -178,6 +178,7 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/public/plugin.tsx#:~:text=environment) | 8.8.0 | +| | [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/public/plugin.tsx#:~:text=identifyUser), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/server/plugin.ts#:~:text=identifyUser), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/public/plugin.test.ts#:~:text=identifyUser), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/public/plugin.test.ts#:~:text=identifyUser), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/public/plugin.test.ts#:~:text=identifyUser), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/public/plugin.test.ts#:~:text=identifyUser), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/server/plugin.test.ts#:~:text=identifyUser), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/server/plugin.test.ts#:~:text=identifyUser), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/server/plugin.test.ts#:~:text=identifyUser), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/server/plugin.test.ts#:~:text=identifyUser) | - | | | [chat.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/server/routes/chat.ts#:~:text=authc) | - | @@ -430,7 +431,6 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=indexPatternId) | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [mounter.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/mounter.tsx#:~:text=onAppLeave) | 8.8.0 | @@ -655,11 +655,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | | | [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), [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), [list.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.test.ts#:~:text=mode), [response_actions.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts#:~:text=mode)+ 3 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), [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), [list.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.test.ts#:~:text=mode), [response_actions.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts#:~:text=mode)+ 3 more | 8.8.0 | +| | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts#:~:text=license%24) | 8.8.0 | | | [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [create_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts#:~:text=authc), [delete_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts#:~:text=authc), [finalize_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts#:~:text=authc), [open_close_signals_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts#:~:text=authc), [preview_rules_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts#:~:text=authc), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts#:~:text=authc) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/index.tsx#:~:text=onAppLeave), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/plugin.tsx#:~:text=onAppLeave) | 8.8.0 | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx#:~:text=AppLeaveHandler), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx#:~:text=AppLeaveHandler), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/types.ts#:~:text=AppLeaveHandler), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/types.ts#:~:text=AppLeaveHandler), [routes.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/routes.tsx#:~:text=AppLeaveHandler), [routes.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/routes.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler) | 8.8.0 | -| | [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes) | - | -| | [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes) | - | +| | [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [saved_objects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/saved_objects.ts#:~:text=SavedObjectAttributes), [saved_objects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/saved_objects.ts#:~:text=SavedObjectAttributes) | - | +| | [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [saved_objects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/saved_objects.ts#:~:text=SavedObjectAttributes), [saved_objects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/saved_objects.ts#:~:text=SavedObjectAttributes) | - | @@ -689,7 +690,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/rule_type.test.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/rule_type.test.ts#:~:text=fetch) | - | -| | [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=indexPatterns), [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns) | - | +| | [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=indexPatterns), [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns) | - | | | [expression.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx#:~:text=fieldFormats) | - | | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/rule_type.test.ts#:~:text=fetch), [rule_type.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/stack_alerts/server/alert_types/es_query/rule_type.test.ts#:~:text=fetch) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 6275c5e9b7f53..7d2110b109555 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -167,6 +167,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | --------|-------|-----------|-----------| | securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [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), [list.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.test.ts#:~:text=mode), [response_actions.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts#:~:text=mode)+ 3 more | 8.8.0 | | securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [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), [list.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.test.ts#:~:text=mode), [response_actions.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts#:~:text=mode)+ 3 more | 8.8.0 | +| securitySolution | | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts#:~:text=license%24) | 8.8.0 | | securitySolution | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/index.tsx#:~:text=onAppLeave), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/plugin.tsx#:~:text=onAppLeave) | 8.8.0 | | securitySolution | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx#:~:text=AppLeaveHandler), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx#:~:text=AppLeaveHandler), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/types.ts#:~:text=AppLeaveHandler), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/types.ts#:~:text=AppLeaveHandler), [routes.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/routes.tsx#:~:text=AppLeaveHandler), [routes.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/routes.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler) | 8.8.0 | @@ -193,5 +194,5 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| lens | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [mounter.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/mounter.tsx#:~:text=onAppLeave), [visualize_top_nav.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx#:~:text=onAppLeave), [visualize_editor_common.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx#:~:text=onAppLeave), [app.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/visualizations/public/visualize_app/app.tsx#:~:text=onAppLeave), [index.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/visualizations/public/visualize_app/index.tsx#:~:text=onAppLeave) | 8.8.0 | -| management | | [application.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/management/public/application.tsx#:~:text=appBasePath) | 8.8.0 | \ No newline at end of file +| management | | [application.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/management/public/application.tsx#:~:text=appBasePath) | 8.8.0 | +| visualizations | | [visualize_top_nav.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx#:~:text=onAppLeave), [visualize_editor_common.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx#:~:text=onAppLeave), [app.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/visualizations/public/visualize_app/app.tsx#:~:text=onAppLeave), [index.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/visualizations/public/visualize_app/index.tsx#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [mounter.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/mounter.tsx#:~:text=onAppLeave) | 8.8.0 | \ No newline at end of file diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index f43e7dccfe704..cb50c61bce67d 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: 2022-09-20 +date: 2022-09-22 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 c9ebfdee37434..695319775e984 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -312,10 +312,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" - }, { "plugin": "observability", "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_discover_link.tsx" @@ -362,6 +358,27 @@ } ] }, + { + "parentPluginId": "discover", + "id": "def-public.DiscoverAppLocatorParams.dataViewSpec", + "type": "Object", + "tags": [], + "label": "dataViewSpec", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + " | undefined" + ], + "path": "src/plugins/discover/public/locator.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "discover", "id": "def-public.DiscoverAppLocatorParams.timeRange", diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index d0066edd11e12..ad00f12b57cf8 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-disco | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 96 | 0 | 79 | 4 | +| 97 | 0 | 80 | 4 | ## Client diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 7b8b8601af087..694a717b00ea4 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index d938439ede797..cd86fe18bd9c8 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: 2022-09-20 +date: 2022-09-22 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 bf199f204fba2..a3ea1b1190f83 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: 2022-09-20 +date: 2022-09-22 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 19aca55bed367..08cb65de4ffb1 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: 2022-09-20 +date: 2022-09-22 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 a7d4bd56a4065..69819eb7b668c 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: 2022-09-20 +date: 2022-09-22 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 24b08955cb1ab..1e0dae9b242e7 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.devdocs.json b/api_docs/event_annotation.devdocs.json index 35e2988d5b946..0bf1025c1dd96 100644 --- a/api_docs/event_annotation.devdocs.json +++ b/api_docs/event_annotation.devdocs.json @@ -607,7 +607,7 @@ "label": "AvailableAnnotationIcon", "description": [], "signature": [ - "\"circle\" | \"asterisk\" | \"alert\" | \"bell\" | \"bolt\" | \"bug\" | \"editorComment\" | \"flag\" | \"heart\" | \"mapMarker\" | \"pinFilled\" | \"starEmpty\" | \"tag\" | \"triangle\"" + "\"circle\" | \"asterisk\" | \"alert\" | \"bell\" | \"bolt\" | \"bug\" | \"editorComment\" | \"flag\" | \"heart\" | \"mapMarker\" | \"pinFilled\" | \"starEmpty\" | \"starFilled\" | \"tag\" | \"triangle\"" ], "path": "src/plugins/event_annotation/common/types.ts", "deprecated": false, @@ -1361,7 +1361,7 @@ "label": "options", "description": [], "signature": [ - "(\"circle\" | \"asterisk\" | \"alert\" | \"bell\" | \"bolt\" | \"bug\" | \"editorComment\" | \"flag\" | \"heart\" | \"mapMarker\" | \"pinFilled\" | \"starEmpty\" | \"tag\" | \"triangle\")[]" + "(\"circle\" | \"asterisk\" | \"alert\" | \"bell\" | \"bolt\" | \"bug\" | \"editorComment\" | \"flag\" | \"heart\" | \"mapMarker\" | \"pinFilled\" | \"starEmpty\" | \"starFilled\" | \"tag\" | \"triangle\")[]" ], "path": "src/plugins/event_annotation/common/manual_event_annotation/index.ts", "deprecated": false, @@ -2471,7 +2471,7 @@ "label": "options", "description": [], "signature": [ - "(\"circle\" | \"asterisk\" | \"alert\" | \"bell\" | \"bolt\" | \"bug\" | \"editorComment\" | \"flag\" | \"heart\" | \"mapMarker\" | \"pinFilled\" | \"starEmpty\" | \"tag\" | \"triangle\")[]" + "(\"circle\" | \"asterisk\" | \"alert\" | \"bell\" | \"bolt\" | \"bug\" | \"editorComment\" | \"flag\" | \"heart\" | \"mapMarker\" | \"pinFilled\" | \"starEmpty\" | \"starFilled\" | \"tag\" | \"triangle\")[]" ], "path": "src/plugins/event_annotation/common/query_point_event_annotation/index.ts", "deprecated": false, diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index d5e087678a1d0..32e9439e61b31 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index d730f68e18eab..32bf0c0ec8138 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index a3ee129f44d7a..ce92668b511a0 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: 2022-09-20 +date: 2022-09-22 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 3b708aa6953ee..7c7a8804ab79f 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: 2022-09-20 +date: 2022-09-22 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 3a813a40fc8f4..1927d1ba583e9 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: 2022-09-20 +date: 2022-09-22 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 9c315a54f4499..50164ed6d0dc8 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: 2022-09-20 +date: 2022-09-22 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 b821534dec8b8..04246f0e5bd22 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: 2022-09-20 +date: 2022-09-22 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 45ed0cf31366f..6226327af0e0b 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: 2022-09-20 +date: 2022-09-22 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 c9463ec11ba52..6240f18c0ce55 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: 2022-09-20 +date: 2022-09-22 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 1518c761b6a23..633b28f5cb989 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: 2022-09-20 +date: 2022-09-22 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 0f12cfcfe0367..783dcdff3412b 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: 2022-09-20 +date: 2022-09-22 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 74cf96e0e3b2c..2e2068e4bd83b 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: 2022-09-20 +date: 2022-09-22 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 2897faff88580..0950fff727a5f 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: 2022-09-20 +date: 2022-09-22 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 c4dd54fe09f32..e9d5af42e5be2 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.devdocs.json b/api_docs/expression_x_y.devdocs.json index 46ce5d448b1e5..70749833d4563 100644 --- a/api_docs/expression_x_y.devdocs.json +++ b/api_docs/expression_x_y.devdocs.json @@ -1939,7 +1939,7 @@ "label": "AvailableReferenceLineIcon", "description": [], "signature": [ - "\"circle\" | \"asterisk\" | \"alert\" | \"bell\" | \"bolt\" | \"bug\" | \"editorComment\" | \"flag\" | \"heart\" | \"mapMarker\" | \"pinFilled\" | \"starEmpty\" | \"tag\" | \"triangle\" | \"empty\"" + "\"circle\" | \"asterisk\" | \"alert\" | \"bell\" | \"bolt\" | \"bug\" | \"editorComment\" | \"flag\" | \"heart\" | \"mapMarker\" | \"pinFilled\" | \"starEmpty\" | \"starFilled\" | \"tag\" | \"triangle\" | \"empty\"" ], "path": "src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts", "deprecated": false, diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index d94ee04294bb3..f83d943cbcd24 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: 2022-09-20 +date: 2022-09-22 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 3d4b511820a01..e186179b019e0 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: 2022-09-20 +date: 2022-09-22 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 4589136f7b707..f917d8d7d9967 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: 2022-09-20 +date: 2022-09-22 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 0583d1db89a25..171e1798f6ecf 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 260869a64bd85..b89efdc3b0f69 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index faee578b74688..69eb98792d527 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -2,7 +2,133 @@ "id": "files", "client": { "classes": [], - "functions": [], + "functions": [ + { + "parentPluginId": "files", + "id": "def-public.FilesContext", + "type": "Function", + "tags": [], + "label": "FilesContext", + "description": [], + "signature": [ + "({ children }: { children?: React.ReactNode; }) => JSX.Element" + ], + "path": "x-pack/plugins/files/public/components/context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.FilesContext.$1", + "type": "Object", + "tags": [], + "label": "{ children }", + "description": [], + "signature": [ + "{ children?: React.ReactNode; }" + ], + "path": "x-pack/plugins/files/public/components/context.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.Image", + "type": "Function", + "tags": [ + "note" + ], + "label": "Image", + "description": [ + "\nA viewport-aware component that displays an image. This component is a very\nthin wrapper around the img tag.\n" + ], + "signature": [ + "React.ForwardRefExoticComponent<", + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + " & React.RefAttributes>" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Image.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.UploadFile", + "type": "Function", + "tags": [], + "label": "UploadFile", + "description": [], + "signature": [ + "(props: ", + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + ") => JSX.Element" + ], + "path": "x-pack/plugins/files/public/components/upload_file/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.UploadFile.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + "" + ], + "path": "x-pack/plugins/files/public/components/upload_file/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [ { "parentPluginId": "files", @@ -62,7 +188,7 @@ "- create file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -97,7 +223,7 @@ "- delete file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -140,7 +266,7 @@ "- get file by ID args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -191,7 +317,7 @@ "- list files args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -234,7 +360,7 @@ "- update file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -252,7 +378,7 @@ "\nStream the contents of the file to Kibana server for storage.\n" ], "signature": [ - "(args: { body: unknown; } & { id: string; } & { kind: string; }) => Promise<{ ok: true; size: number; }>" + "(args: { body: unknown; } & { id: string; } & { selfDestructOnAbort?: boolean | undefined; } & { kind: string; } & { abortSignal?: AbortSignal | undefined; }) => Promise<{ ok: true; size: number; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -269,7 +395,7 @@ "- upload file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -304,7 +430,7 @@ "- download file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -398,7 +524,7 @@ "- File share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -433,7 +559,7 @@ "- File unshare arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -476,7 +602,7 @@ "- Get file share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -527,7 +653,7 @@ "- Get file share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; }" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -621,6 +747,270 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props", + "type": "Interface", + "tags": [], + "label": "Props", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + " extends React.ImgHTMLAttributes" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.src", + "type": "string", + "tags": [], + "label": "src", + "description": [], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.alt", + "type": "string", + "tags": [], + "label": "alt", + "description": [], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onFirstVisible", + "type": "Function", + "tags": [], + "label": "onFirstVisible", + "description": [ + "\nEmits when the image first becomes visible" + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props", + "type": "Interface", + "tags": [], + "label": "Props", + "description": [ + "\nUploadFile component props" + ], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + "" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.kind", + "type": "Uncategorized", + "tags": [], + "label": "kind", + "description": [ + "\nA file kind that should be registered during plugin startup. See {@link FileServiceStart}." + ], + "signature": [ + "Kind" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [ + "\nA files client that will be used process uploads." + ], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.FilesClient", + "text": "FilesClient" + } + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.allowClear", + "type": "CompoundType", + "tags": [ + "note" + ], + "label": "allowClear", + "description": [ + "\nAllow users to clear a file after uploading.\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.immediate", + "type": "CompoundType", + "tags": [], + "label": "immediate", + "description": [ + "\nStart uploading the file as soon as it is provided\nby the user." + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.meta", + "type": "Object", + "tags": [], + "label": "meta", + "description": [ + "\nMetadata that you want to associate with any uploaded files" + ], + "signature": [ + "Record | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.allowRepeatedUploads", + "type": "CompoundType", + "tags": [ + "default" + ], + "label": "allowRepeatedUploads", + "description": [ + "\nWhether this component should display a \"done\" state after processing an\nupload or return to the initial state to allow for another upload.\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onDone", + "type": "Function", + "tags": [], + "label": "onDone", + "description": [ + "\nCalled when the an upload process fully completes" + ], + "signature": [ + "(files: UploadedFile[]) => void" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.onDone.$1", + "type": "Array", + "tags": [], + "label": "files", + "description": [], + "signature": [ + "UploadedFile[]" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onError", + "type": "Function", + "tags": [], + "label": "onError", + "description": [ + "\nCalled when an error occurs during upload" + ], + "signature": [ + "((e: Error) => void) | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.onError.$1", + "type": "Object", + "tags": [], + "label": "e", + "description": [], + "signature": [ + "Error" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false } ], "enums": [], @@ -762,7 +1152,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; upload: (arg: Omit<{ body: unknown; } & { id: string; } & { kind: string; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit<{ id: string; fileName?: string | undefined; } & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit<", + "; }>; upload: (arg: Omit<{ body: unknown; } & { id: string; } & { selfDestructOnAbort?: boolean | undefined; } & { kind: string; } & { abortSignal?: AbortSignal | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit<{ id: string; fileName?: string | undefined; } & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit<", { "pluginId": "files", "scope": "common", @@ -835,6 +1225,95 @@ } ], "objects": [], + "setup": { + "parentPluginId": "files", + "id": "def-public.FilesSetup", + "type": "Interface", + "tags": [], + "label": "FilesSetup", + "description": [ + "\nPublic setup-phase contract" + ], + "path": "x-pack/plugins/files/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.FilesSetup.filesClientFactory", + "type": "Object", + "tags": [], + "label": "filesClientFactory", + "description": [ + "\nA factory for creating an {@link FilesClient} instance. This requires a\nregistered {@link FileKind}." + ], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.FilesClientFactory", + "text": "FilesClientFactory" + } + ], + "path": "x-pack/plugins/files/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.FilesSetup.registerFileKind", + "type": "Function", + "tags": [], + "label": "registerFileKind", + "description": [ + "\nRegister a {@link FileKind} which allows for specifying details about the files\nthat will be uploaded.\n" + ], + "signature": [ + "(fileKind: ", + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + }, + ") => void" + ], + "path": "x-pack/plugins/files/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.FilesSetup.registerFileKind.$1", + "type": "Object", + "tags": [], + "label": "fileKind", + "description": [ + "- the file kind to register" + ], + "signature": [ + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + } + ], + "path": "x-pack/plugins/files/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "lifecycle": "setup", + "initialIsOpen": true + }, "start": { "parentPluginId": "files", "id": "def-public.FilesStart", @@ -843,13 +1322,15 @@ "label": "FilesStart", "description": [], "signature": [ + "{ filesClientFactory: ", { "pluginId": "files", "scope": "public", "docId": "kibFilesPluginApi", - "section": "def-public.FilesSetup", - "text": "FilesSetup" - } + "section": "def-public.FilesClientFactory", + "text": "FilesClientFactory" + }, + "; }" ], "path": "x-pack/plugins/files/public/plugin.ts", "deprecated": false, diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 98d50b6c79efc..ea2cf22087412 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; @@ -21,13 +21,19 @@ Contact [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 240 | 0 | 6 | 2 | +| 263 | 0 | 15 | 2 | ## Client +### Setup + + ### Start +### Functions + + ### Interfaces diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 24e0f4dce933a..66f288b6ca432 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -5690,6 +5690,167 @@ ], "returnComment": [] }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkUpdate", + "type": "Function", + "tags": [], + "label": "bulkUpdate", + "description": [], + "signature": [ + "(soClient: ", + "SavedObjectsClientContract", + ", esClient: ", + "ElasticsearchClient", + ", packagePolicyUpdates: (", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.NewPackagePolicy", + "text": "NewPackagePolicy" + }, + " & { version?: string | undefined; id: string; })[], options?: { user?: ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + " | undefined; force?: boolean | undefined; } | undefined, currentVersion?: string | undefined) => Promise<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackagePolicy", + "text": "PackagePolicy" + }, + "[] | null>" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkUpdate.$1", + "type": "Object", + "tags": [], + "label": "soClient", + "description": [], + "signature": [ + "SavedObjectsClientContract" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkUpdate.$2", + "type": "Object", + "tags": [], + "label": "esClient", + "description": [], + "signature": [ + "ElasticsearchClient" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkUpdate.$3", + "type": "Array", + "tags": [], + "label": "packagePolicyUpdates", + "description": [], + "signature": [ + "(", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.NewPackagePolicy", + "text": "NewPackagePolicy" + }, + " & { version?: string | undefined; id: string; })[]" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkUpdate.$4", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkUpdate.$4.user", + "type": "Object", + "tags": [], + "label": "user", + "description": [], + "signature": [ + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkUpdate.$4.force", + "type": "CompoundType", + "tags": [], + "label": "force", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkUpdate.$5", + "type": "string", + "tags": [], + "label": "currentVersion", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, { "parentPluginId": "fleet", "id": "def-server.PackagePolicyClient.get", @@ -8856,6 +9017,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.EpmPackageAdditions.licensePath", + "type": "string", + "tags": [], + "label": "licensePath", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.EpmPackageAdditions.keepPoliciesUpToDate", @@ -12122,6 +12297,48 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-common.PackageSpecConditions", + "type": "Interface", + "tags": [], + "label": "PackageSpecConditions", + "description": [], + "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.PackageSpecConditions.kibana", + "type": "Object", + "tags": [], + "label": "kibana", + "description": [], + "signature": [ + "{ version: string; }" + ], + "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.PackageSpecConditions.elastic", + "type": "Object", + "tags": [], + "label": "elastic", + "description": [], + "signature": [ + "{ subscription: string; } | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-common.PackageSpecIcon", @@ -12269,6 +12486,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.PackageSpecManifest.source", + "type": "Object", + "tags": [], + "label": "source", + "description": [], + "signature": [ + "{ license: string; } | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.PackageSpecManifest.type", @@ -13827,7 +14058,7 @@ "label": "DocAssetType", "description": [], "signature": [ - "\"notice\" | \"doc\"" + "\"license\" | \"notice\" | \"doc\"" ], "path": "x-pack/plugins/fleet/common/types/models/epm.ts", "deprecated": false, @@ -14859,21 +15090,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "fleet", - "id": "def-common.PackageSpecConditions", - "type": "Type", - "tags": [], - "label": "PackageSpecConditions", - "description": [], - "signature": [ - "{ kibana: { version: string; }; }" - ], - "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "fleet", "id": "def-common.PLUGIN_ID", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 398f9e82fd03a..deaa296249ab3 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Fleet](https://github.com/orgs/elastic/teams/fleet) for questions regar | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 974 | 3 | 874 | 17 | +| 986 | 3 | 886 | 17 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index c71484128ec3b..0aae391e045d8 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: 2022-09-20 +date: 2022-09-22 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 e7625a590a461..c1d48c0989a34 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: 2022-09-20 +date: 2022-09-22 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 381a739739e54..20dac194b7666 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index ed831349e52bd..ea1e351b8eca7 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: 2022-09-20 +date: 2022-09-22 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 c8af6660752e5..14dbc7f48fedb 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: 2022-09-20 +date: 2022-09-22 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 27f5c3569fa49..e9ee4c12f506d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 18fb173160a21..83ec164aa79e6 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 9056c00d72b84..475c42ddf450b 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 9858dd71b40a0..efdce690d2247 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index d20a7b9ad37dd..26ee28f0d5116 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 091fed52214f6..047d85e585bb0 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index adcc61574b831..711dca1d69319 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index d946f4c837ceb..f2d1444eddc39 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index d39f68906c4e7..c4b2f07e35477 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index cd98524289903..66f9a16b7e217 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: 2022-09-20 +date: 2022-09-22 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 fe6b3d629f334..3ec92012afe72 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: 2022-09-20 +date: 2022-09-22 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 b5f8465d23abb..6081a36a42fdc 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: 2022-09-20 +date: 2022-09-22 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 f0bf230c94525..ffb73d2b3cbbc 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: 2022-09-20 +date: 2022-09-22 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 e05ed7932ea0b..145d493a2664f 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 9f1463f50f008..8670cc5eb9306 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 350dfc4f21573..791dbb286d079 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: 2022-09-20 +date: 2022-09-22 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 fe78eade4d5e8..c240caf2e87dd 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 1fd328a1909af..6e1955e64d291 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: 2022-09-20 +date: 2022-09-22 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 36f3f627a7079..548d3a429b221 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: 2022-09-20 +date: 2022-09-22 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 66064bc9235db..f71fdfc630e12 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: 2022-09-20 +date: 2022-09-22 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 50c89a112f318..6a78799b95ef2 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: 2022-09-20 +date: 2022-09-22 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 946811ebef1ef..92b6b367bc9ff 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index e1e61842963b9..235779d14f01d 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: 2022-09-20 +date: 2022-09-22 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 f4e1d6df66d36..754221c503859 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: 2022-09-20 +date: 2022-09-22 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 3dfe88a5fe6f8..85bc4f51eec07 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 770caaadc3273..cb3f886dd7e36 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 88c8770f78e05..f9b256a70ba39 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 9985d16c3d530..6ffaa576b57fe 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 8b30598c799c5..3966c5e685595 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: 2022-09-20 +date: 2022-09-22 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 7d9ba2b790cd9..de23002284e5c 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 367e2d1efc34b..c4777e8cf10ee 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 3bc7041adc0c7..f89a0fcf9a39a 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: 2022-09-20 +date: 2022-09-22 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 04c9d11050656..9c41e734fcb99 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: 2022-09-20 +date: 2022-09-22 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 d334e274ebd27..0cea1dc774c2a 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: 2022-09-20 +date: 2022-09-22 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 fb1732696cd58..cfae804acde4e 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: 2022-09-20 +date: 2022-09-22 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 5841b62fe8421..8de77b355c88a 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: 2022-09-20 +date: 2022-09-22 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 d24d2537e5a29..9cb6865cfc80d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index ac8c8304cbacd..21a8ec5b98cf7 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: 2022-09-20 +date: 2022-09-22 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 d379d0650b330..30982dcb656d0 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: 2022-09-20 +date: 2022-09-22 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_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 1532b48e1caa7..9e5b655ab4d8f 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: 2022-09-20 +date: 2022-09-22 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 7ae08ec040bc7..2ae5b314e3ab7 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: 2022-09-20 +date: 2022-09-22 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 a46e1f96ce169..5de0d422641c3 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: 2022-09-20 +date: 2022-09-22 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 c4ade5b2f59e7..f54426285ba57 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: 2022-09-20 +date: 2022-09-22 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 734a7db43b735..3307668b79611 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: 2022-09-20 +date: 2022-09-22 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 b039c307cc98d..f5bb2de27d66d 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: 2022-09-20 +date: 2022-09-22 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 e53c2bfaa2f7c..97d5a26685f3a 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: 2022-09-20 +date: 2022-09-22 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 dba1d83d54bfa..ece7b5cdbf4fe 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 0b8ccf9d5cc52..2a4053b22f8f0 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: 2022-09-20 +date: 2022-09-22 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 c9b340d67fa0e..66bc99d5a3e6a 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: 2022-09-20 +date: 2022-09-22 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 00e6556403379..a3818855fb04c 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: 2022-09-20 +date: 2022-09-22 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_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 8c6a0d2cc5326..f9d1268dfac82 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: 2022-09-20 +date: 2022-09-22 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 fcf11ae9ddef9..22cd35d5bd484 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: 2022-09-20 +date: 2022-09-22 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 9c728cc39d05c..b4f237ef1b22f 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: 2022-09-20 +date: 2022-09-22 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 57a9e5d36f00e..60166d8b9529b 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: 2022-09-20 +date: 2022-09-22 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 579e0c95cedd1..e133115b93e4e 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: 2022-09-20 +date: 2022-09-22 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 6f67e55a17c96..19749a36a463f 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: 2022-09-20 +date: 2022-09-22 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 9f3766a0e2e9a..a0793ce78d4cc 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: 2022-09-20 +date: 2022-09-22 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 f3c49ab467f46..ed3da76740aa1 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: 2022-09-20 +date: 2022-09-22 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 601595113c8bd..f7b43d5d2e357 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: 2022-09-20 +date: 2022-09-22 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 b8dbcf387129b..7554694a7e97e 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: 2022-09-20 +date: 2022-09-22 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 d9e077756915d..f4bde1777de85 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: 2022-09-20 +date: 2022-09-22 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 9de0e77f1d83b..28b7898bfeacc 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 7b77b445df55e..d6a769f16193d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 226141067a8d8..05d63bea9feff 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 44deabd806282..0c7d773da623c 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: 2022-09-20 +date: 2022-09-22 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 4588b1e74c0fc..14f381a2ebf56 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: 2022-09-20 +date: 2022-09-22 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 14d84cfd0c01e..0cfbea89cd0c5 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: 2022-09-20 +date: 2022-09-22 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 34101622d0bb1..741db5f261f88 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: 2022-09-20 +date: 2022-09-22 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 8412cea09e8dc..0c91019f2e6b9 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: 2022-09-20 +date: 2022-09-22 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 a488597af297c..ae8cf6e8b9770 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: 2022-09-20 +date: 2022-09-22 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 906e6aa1e6d33..d5db7822402e6 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: 2022-09-20 +date: 2022-09-22 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 25cbf34eaceb0..3825cd5ba57fe 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: 2022-09-20 +date: 2022-09-22 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 430afd1e0013b..7e445c7f999d7 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: 2022-09-20 +date: 2022-09-22 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 34072ca6dfde7..77f5a3959fbe7 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: 2022-09-20 +date: 2022-09-22 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 4d424546b14b7..901253890c7e9 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: 2022-09-20 +date: 2022-09-22 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 4ea933853df2d..deaa4702f705c 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: 2022-09-20 +date: 2022-09-22 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 397f9e99b4924..140d9132ec2c0 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: 2022-09-20 +date: 2022-09-22 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 c781ba9fff5a3..b0c9c4220e7f5 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: 2022-09-20 +date: 2022-09-22 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 0be92db2558f6..e3a0724e0dcb9 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: 2022-09-20 +date: 2022-09-22 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 06d2f6cda8dbb..fcbeeaa6e9627 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: 2022-09-20 +date: 2022-09-22 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 1bccc28f3add7..f7102c3ec16e0 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: 2022-09-20 +date: 2022-09-22 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 889dd33ec6144..e60af9f5d9eab 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: 2022-09-20 +date: 2022-09-22 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_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index d6bf834e28d3f..e8d711e575cc6 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 02de9f766f698..5352c783a9b45 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index fd40adbec69d3..8e81de35d50fb 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index c7c396db97c20..9fe6811754183 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: 2022-09-20 +date: 2022-09-22 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 ca70a925fbb56..a0cf2e4ffbfa1 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: 2022-09-20 +date: 2022-09-22 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 3660bcc446cf5..56c0f34c4227e 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: 2022-09-20 +date: 2022-09-22 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 75e85149ca6c4..78b7a706f203d 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: 2022-09-20 +date: 2022-09-22 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 a5f96ecfd44b4..6965e9b7a4328 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: 2022-09-20 +date: 2022-09-22 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 497956f0ee8a3..63c27ceafd0c2 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: 2022-09-20 +date: 2022-09-22 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 24e3538122a9d..3064d37b2b764 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: 2022-09-20 +date: 2022-09-22 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.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 58b512d1545c7..fc9a42796ed91 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.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 de5f52b8471e4..c1c9212ced012 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: 2022-09-20 +date: 2022-09-22 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 0637bb9d8925e..676f01fc2cf5b 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: 2022-09-20 +date: 2022-09-22 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 3e7b806dc160a..c693554b5408d 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: 2022-09-20 +date: 2022-09-22 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_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 55eb0c12108de..11c07ea3a463d 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: 2022-09-20 +date: 2022-09-22 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 f50fbbca7396c..1d4b36eb97b55 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: 2022-09-20 +date: 2022-09-22 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 a674bd9826403..244a90780255a 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: 2022-09-20 +date: 2022-09-22 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 24e800f05030e..95b139093f28d 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: 2022-09-20 +date: 2022-09-22 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 6d04faa001d5a..0464c4b38c300 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: 2022-09-20 +date: 2022-09-22 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 67c149514411e..93cf2d5749ca3 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: 2022-09-20 +date: 2022-09-22 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 aae45e4a49448..225a82aa4be21 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: 2022-09-20 +date: 2022-09-22 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 99a09376c4b9b..49bcb201c222a 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: 2022-09-20 +date: 2022-09-22 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 fb1266aaba745..e114e2af0b6bc 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: 2022-09-20 +date: 2022-09-22 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 b3f8467f9f307..a1ce5e75dfdad 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: 2022-09-20 +date: 2022-09-22 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 d0b884478e8d8..04163a37acbf9 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: 2022-09-20 +date: 2022-09-22 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 4374729c68ce3..4f794d1bce300 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: 2022-09-20 +date: 2022-09-22 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.devdocs.json b/api_docs/kbn_core_notifications_browser.devdocs.json index b69e0619012a3..c9bcfa0a110d8 100644 --- a/api_docs/kbn_core_notifications_browser.devdocs.json +++ b/api_docs/kbn_core_notifications_browser.devdocs.json @@ -681,7 +681,7 @@ "signature": [ "Pick<", "Toast", - ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\"> & { title?: string | ", + ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\" | \"css\"> & { title?: string | ", "MountPoint", " | undefined; text?: string | ", "MountPoint", @@ -728,7 +728,7 @@ "signature": [ "Pick<", "Toast", - ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\"> & { title?: string | ", + ", \"children\" | \"onError\" | \"hidden\" | \"color\" | \"className\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"toastLifeTimeMs\" | \"iconType\" | \"onClose\" | \"data-test-subj\" | \"css\"> & { title?: string | ", "MountPoint", " | undefined; text?: string | ", "MountPoint", diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index ed6b7c356b79f..11a6a77f6a8b1 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: 2022-09-20 +date: 2022-09-22 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 4e05d51b17b25..12bc341027894 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: 2022-09-20 +date: 2022-09-22 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 0b13cdbd31306..effecee9ba65b 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: 2022-09-20 +date: 2022-09-22 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 8f1a0f8bbbbad..2e03ba6fcc3ed 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: 2022-09-20 +date: 2022-09-22 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 15b9282a5911d..7fba986174a28 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: 2022-09-20 +date: 2022-09-22 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 1e7f0b4206b89..4c463966e38b2 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: 2022-09-20 +date: 2022-09-22 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_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 7e9afcaea1185..e6d5f7d5a1811 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: 2022-09-20 +date: 2022-09-22 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 0aea4d9e2256a..bc67c83d3d8ae 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: 2022-09-20 +date: 2022-09-22 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 87ef4b638ae7b..c02b5f7e8791c 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: 2022-09-20 +date: 2022-09-22 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_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index 2e6c45ada0542..b12a698b21606 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -248,6 +248,131 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteOptions", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteOptions", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteOptions.force", + "type": "CompoundType", + "tags": [], + "label": "force", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteResponse", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteResponse", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteResponse.statuses", + "type": "Array", + "tags": [], + "label": "statuses", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-browser", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiBrowserPluginApi", + "section": "def-common.SavedObjectsBulkDeleteResponseItem", + "text": "SavedObjectsBulkDeleteResponseItem" + }, + "[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteResponseItem", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteResponseItem", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteResponseItem.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteResponseItem.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteResponseItem.success", + "type": "boolean", + "tags": [], + "label": "success", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsBulkDeleteResponseItem.error", + "type": "Object", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "SavedObjectError", + " | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-api-browser", "id": "def-common.SavedObjectsBulkResolveResponse", @@ -678,6 +803,87 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsClientContract.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\nDeletes multiple documents at once" + ], + "signature": [ + "(objects: ", + "SavedObjectTypeIdTuple", + "[], options?: ", + { + "pluginId": "@kbn/core-saved-objects-api-browser", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiBrowserPluginApi", + "section": "def-common.SavedObjectsBulkDeleteOptions", + "text": "SavedObjectsBulkDeleteOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "@kbn/core-saved-objects-api-browser", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiBrowserPluginApi", + "section": "def-common.SavedObjectsBulkDeleteResponse", + "text": "SavedObjectsBulkDeleteResponse" + }, + ">" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsClientContract.bulkDelete.$1", + "type": "Array", + "tags": [], + "label": "objects", + "description": [ + "- an array of objects containing id, type" + ], + "signature": [ + "SavedObjectTypeIdTuple", + "[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsClientContract.bulkDelete.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [ + "- optional force argument to force deletion of objects in a namespace other than the scoped client" + ], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-browser", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiBrowserPluginApi", + "section": "def-common.SavedObjectsBulkDeleteOptions", + "text": "SavedObjectsBulkDeleteOptions" + }, + " | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [ + "The bulk delete result for the saved objects for the given types and ids." + ] + }, { "parentPluginId": "@kbn/core-saved-objects-api-browser", "id": "def-common.SavedObjectsClientContract.find", diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index fb16daa874388..8fcc6aac79dd3 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 94 | 1 | 66 | 0 | +| 106 | 1 | 75 | 0 | ## Common diff --git a/api_docs/kbn_core_saved_objects_api_server.devdocs.json b/api_docs/kbn_core_saved_objects_api_server.devdocs.json index 74d6732ba03ba..0e040b72d2d3d 100644 --- a/api_docs/kbn_core_saved_objects_api_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server.devdocs.json @@ -444,6 +444,97 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.ISavedObjectsRepository.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\nDeletes multiple documents at once" + ], + "signature": [ + "(objects: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteObject", + "text": "SavedObjectsBulkDeleteObject" + }, + "[], options?: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteOptions", + "text": "SavedObjectsBulkDeleteOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteResponse", + "text": "SavedObjectsBulkDeleteResponse" + }, + ">" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.ISavedObjectsRepository.bulkDelete.$1", + "type": "Array", + "tags": [], + "label": "objects", + "description": [ + "- an array of objects containing id and type" + ], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteObject", + "text": "SavedObjectsBulkDeleteObject" + }, + "[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.ISavedObjectsRepository.bulkDelete.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteOptions", + "text": "SavedObjectsBulkDeleteOptions" + }, + " | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [ + "- { statuses: [{ id, type, success, error: { message } }] }" + ] + }, { "parentPluginId": "@kbn/core-saved-objects-api-server", "id": "def-server.ISavedObjectsRepository.deleteByNamespace", @@ -2131,6 +2222,215 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteObject", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteObject", + "description": [ + "\n" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteObject.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteObject.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteOptions", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteOptions", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteOptions", + "text": "SavedObjectsBulkDeleteOptions" + }, + " extends ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBaseOptions", + "text": "SavedObjectsBaseOptions" + } + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteOptions.refresh", + "type": "CompoundType", + "tags": [], + "label": "refresh", + "description": [ + "The Elasticsearch Refresh setting for this operation" + ], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.MutatingOperationRefreshSetting", + "text": "MutatingOperationRefreshSetting" + }, + " | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteOptions.force", + "type": "CompoundType", + "tags": [], + "label": "force", + "description": [ + "\nForce deletion of all objects that exists in multiple namespaces, applied to all objects." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteResponse", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteResponse", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteResponse.statuses", + "type": "Array", + "tags": [], + "label": "statuses", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteStatus", + "text": "SavedObjectsBulkDeleteStatus" + }, + "[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteStatus", + "type": "Interface", + "tags": [], + "label": "SavedObjectsBulkDeleteStatus", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteStatus.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteStatus.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteStatus.success", + "type": "boolean", + "tags": [], + "label": "success", + "description": [ + "The status of deleting the object: true for deleted, false for error" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsBulkDeleteStatus.error", + "type": "Object", + "tags": [], + "label": "error", + "description": [ + "Reason the object could not be deleted (success is false)" + ], + "signature": [ + "SavedObjectError", + " | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-api-server", "id": "def-server.SavedObjectsBulkGetObject", @@ -2939,6 +3239,93 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsClientContract.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\nDeletes multiple SavedObjects batched together as a single request\n" + ], + "signature": [ + "(objects: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteObject", + "text": "SavedObjectsBulkDeleteObject" + }, + "[], options?: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteOptions", + "text": "SavedObjectsBulkDeleteOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteResponse", + "text": "SavedObjectsBulkDeleteResponse" + }, + ">" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsClientContract.bulkDelete.$1", + "type": "Array", + "tags": [], + "label": "objects", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteObject", + "text": "SavedObjectsBulkDeleteObject" + }, + "[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsClientContract.bulkDelete.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsBulkDeleteOptions", + "text": "SavedObjectsBulkDeleteOptions" + }, + " | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, { "parentPluginId": "@kbn/core-saved-objects-api-server", "id": "def-server.SavedObjectsClientContract.find", diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index f308f99088f93..853fc6b82e6f5 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 289 | 1 | 126 | 0 | +| 308 | 1 | 137 | 0 | ## Server diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.devdocs.json b/api_docs/kbn_core_saved_objects_api_server_internal.devdocs.json index d5a95dee2de38..79c17d6086eef 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server_internal.devdocs.json @@ -278,6 +278,62 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server-internal", + "id": "def-server.SavedObjectsRepository.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\n {@inheritDoc ISavedObjectsRepository.bulkDelete}" + ], + "signature": [ + "(objects: ", + "SavedObjectsBulkDeleteObject", + "[], options?: ", + "SavedObjectsBulkDeleteOptions", + ") => Promise<", + "SavedObjectsBulkDeleteResponse", + ">" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-api-server-internal", + "id": "def-server.SavedObjectsRepository.bulkDelete.$1", + "type": "Array", + "tags": [], + "label": "objects", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteObject", + "[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server-internal", + "id": "def-server.SavedObjectsRepository.bulkDelete.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "SavedObjectsBulkDeleteOptions" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "@kbn/core-saved-objects-api-server-internal", "id": "def-server.SavedObjectsRepository.deleteByNamespace", diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index f0a6438fc4fa6..e53441bfc1185 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 68 | 0 | 49 | 0 | +| 71 | 0 | 51 | 0 | ## Server 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 387d4624b34cb..a17b5a1953925 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: 2022-09-20 +date: 2022-09-22 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 3fd8a5c5e9c37..473834ecc1707 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: 2022-09-20 +date: 2022-09-22 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 96ebbe50c9dbb..c9b9a87273772 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: 2022-09-20 +date: 2022-09-22 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 814bc13c8a0f0..ef40536890d3c 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: 2022-09-20 +date: 2022-09-22 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 321a185f26b0c..d66e50e0cd43b 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: 2022-09-20 +date: 2022-09-22 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 0eff5fe3a0815..dd1720b80cd2a 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 3543ac5cce759..d0e65f194f45f 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: 2022-09-20 +date: 2022-09-22 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 dbb9933bd19ef..2c5eb3d91f286 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: 2022-09-20 +date: 2022-09-22 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 ee4489e6cee50..0ae0cd612b849 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: 2022-09-20 +date: 2022-09-22 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 f3c16660d9b57..301aa937001db 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: 2022-09-20 +date: 2022-09-22 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 181a58b3b26d0..91302dc2f4ad0 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: 2022-09-20 +date: 2022-09-22 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 9e0a46fb69b22..d2bdf0f51e9fe 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: 2022-09-20 +date: 2022-09-22 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 c4872c37e0445..5d601613ed4a7 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server_internal.devdocs.json @@ -212,6 +212,56 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-server-internal", + "id": "def-server.registerBulkDeleteRoute", + "type": "Function", + "tags": [], + "label": "registerBulkDeleteRoute", + "description": [], + "signature": [ + "(router: ", + "InternalSavedObjectRouter", + ", { coreUsageData }: RouteDependencies) => void" + ], + "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-server-internal", + "id": "def-server.registerBulkDeleteRoute.$1", + "type": "Object", + "tags": [], + "label": "router", + "description": [], + "signature": [ + "InternalSavedObjectRouter" + ], + "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-saved-objects-server-internal", + "id": "def-server.registerBulkDeleteRoute.$2", + "type": "Object", + "tags": [], + "label": "{ coreUsageData }", + "description": [], + "signature": [ + "RouteDependencies" + ], + "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-server-internal", "id": "def-server.registerBulkGetRoute", diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index a4e0fa38d323b..f323cae307882 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 66 | 0 | 66 | 4 | +| 69 | 0 | 69 | 4 | ## Server diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 6b1a14d7b5a92..82a4ce2a7aac4 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: 2022-09-20 +date: 2022-09-22 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 cc7cefe0fc9cc..5bac274913abd 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 36290f4685f52..8686d9c3536a9 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: 2022-09-20 +date: 2022-09-22 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 764213f0fcc65..f20257486b238 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: 2022-09-20 +date: 2022-09-22 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 5fa9cf11f74df..f562a422f61aa 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index cd6df67e78030..9db2c460e004e 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: 2022-09-20 +date: 2022-09-22 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 c2bbd12094c04..6906d65c39af0 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: 2022-09-20 +date: 2022-09-22 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 e8be3567313b0..e825685ecdd41 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: 2022-09-20 +date: 2022-09-22 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 6bb448c563b7b..375edb77fda06 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: 2022-09-20 +date: 2022-09-22 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_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index ef68d664af2c1..dd58b41f7aebe 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: 2022-09-20 +date: 2022-09-22 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_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 978f1833da04a..61e989baa7c20 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 8abf444a86f18..5c93551750307 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: 2022-09-20 +date: 2022-09-22 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 9ac7e4f27c121..799e715ef6f35 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: 2022-09-20 +date: 2022-09-22 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 8cdfe923481d2..8b1be60494682 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: 2022-09-20 +date: 2022-09-22 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 c82a3ba9a1c82..93c0682c786c0 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: 2022-09-20 +date: 2022-09-22 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 40f540a5048a6..31025c9fac7de 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: 2022-09-20 +date: 2022-09-22 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 eaeeedee820f9..7ac444b7ef812 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: 2022-09-20 +date: 2022-09-22 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 12ade9596f6e6..f7a68e1a00588 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: 2022-09-20 +date: 2022-09-22 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 931ce804b79eb..4dca929c1363c 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: 2022-09-20 +date: 2022-09-22 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.devdocs.json b/api_docs/kbn_core_usage_data_server.devdocs.json index 49517d65e5019..99ec8f759c0dd 100644 --- a/api_docs/kbn_core_usage_data_server.devdocs.json +++ b/api_docs/kbn_core_usage_data_server.devdocs.json @@ -834,6 +834,104 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-usage-data-server", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.total", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.total'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-usage-data-server", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.default.total", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.default.total'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-usage-data-server", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-usage-data-server", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-usage-data-server", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.custom.total", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.custom.total'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-usage-data-server", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-usage-data-server", + "id": "def-server.CoreUsageStats.apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no", + "type": "number", + "tags": [], + "label": "'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no'", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-usage-data-server", "id": "def-server.CoreUsageStats.apiCalls.savedObjectsCreate.total", diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 8b6294af5a33a..b10ab2782c488 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 146 | 0 | 135 | 0 | +| 153 | 0 | 142 | 0 | ## Server diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 971946e462f2f..90e9bca0ae863 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: 2022-09-20 +date: 2022-09-22 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.devdocs.json b/api_docs/kbn_core_usage_data_server_mocks.devdocs.json index 20f3ce6d5b306..0f3cc66101f7f 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.devdocs.json +++ b/api_docs/kbn_core_usage_data_server_mocks.devdocs.json @@ -83,6 +83,8 @@ "BaseIncrementOptions", "]>; incrementSavedObjectsBulkUpdate: jest.MockInstance, [options: ", "BaseIncrementOptions", + "]>; incrementSavedObjectsBulkDelete: jest.MockInstance, [options: ", + "BaseIncrementOptions", "]>; incrementSavedObjectsCreate: jest.MockInstance, [options: ", "BaseIncrementOptions", "]>; incrementSavedObjectsDelete: jest.MockInstance, [options: ", diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 0b31e5b3a34d7..d6e1d8246e70b 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: 2022-09-20 +date: 2022-09-22 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_crypto.mdx b/api_docs/kbn_crypto.mdx index 5b41d00b6b4d9..5baa0d9007a3b 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: 2022-09-20 +date: 2022-09-22 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 f3389cdd3d2a5..04e8a2a5ad66d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 10bef6d0fe0ef..6a636d981544d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 0341bd60eb0db..98354b22dd3f0 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: 2022-09-20 +date: 2022-09-22 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 9d36428f50b35..7812305c5e84d 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: 2022-09-20 +date: 2022-09-22 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 08ad6b9284437..e66f3d2941b8d 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: 2022-09-20 +date: 2022-09-22 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 703febe0cd967..b90ddfe887f5e 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index b8d975b8f5814..c48ba82c33d21 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -658,7 +658,7 @@ "label": "observability", "description": [], "signature": [ - "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly monitorStatus: string; readonly monitorUptime: string; readonly tlsCertificate: string; readonly uptimeDurationAnomaly: string; readonly monitorLogs: string; readonly analyzeMetrics: string; readonly monitorUptimeSynthetics: string; readonly userExperience: string; readonly createAlerts: string; }" + "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly monitorStatus: string; readonly monitorUptime: string; readonly tlsCertificate: string; readonly uptimeDurationAnomaly: string; readonly monitorLogs: string; readonly analyzeMetrics: string; readonly monitorUptimeSynthetics: string; readonly userExperience: string; readonly createAlerts: string; readonly syntheticsCommandReference: 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 dd0d195749624..ea57e416a51d9 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: 2022-09-20 +date: 2022-09-22 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 2e041d294ebee..08d82611db4c2 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 302ca68691fea..90d36c669fdac 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 66d013d8f0a36..3309781c13f31 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: 2022-09-20 +date: 2022-09-22 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 ab88b5be725c4..866fc7615a3e7 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: 2022-09-20 +date: 2022-09-22 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 00b04a4bbc45f..5c9a1fe11d91a 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.devdocs.json b/api_docs/kbn_es_types.devdocs.json new file mode 100644 index 0000000000000..df40e62edf33b --- /dev/null +++ b/api_docs/kbn_es_types.devdocs.json @@ -0,0 +1,463 @@ +{ + "id": "@kbn/es-types", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.ESSearchOptions", + "type": "Interface", + "tags": [], + "label": "ESSearchOptions", + "description": [], + "path": "packages/kbn-es-types/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.ESSearchOptions.restTotalHitsAsInt", + "type": "boolean", + "tags": [], + "label": "restTotalHitsAsInt", + "description": [], + "path": "packages/kbn-es-types/src/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.AggregateOf", + "type": "Type", + "tags": [], + "label": "AggregateOf", + "description": [], + "signature": [ + "Pick & { adjacency_matrix: { buckets: ({ key: string; doc_count: number; } & SubAggregateOf)[]; }; auto_date_histogram: { interval: string; buckets: ({ key: number; key_as_string: string; doc_count: number; } & SubAggregateOf)[]; }; avg: { value: number | null; value_as_string?: string | undefined; }; avg_bucket: { value: number | null; }; boxplot: { min: number | null; max: number | null; q1: number | null; q2: number | null; q3: number | null; }; bucket_correlation: { value: number | null; }; bucket_count_ks_test: { less: number; greater: number; two_sided: number; }; bucket_script: { value: unknown; }; cardinality: { value: number; }; children: { doc_count: number; } & SubAggregateOf; composite: { after_key: CompositeKeysOf; buckets: ({ doc_count: number; key: CompositeKeysOf; } & SubAggregateOf)[]; }; cumulative_cardinality: { value: number; }; cumulative_sum: { value: number; }; date_histogram: MaybeKeyed, string>; date_range: MaybeKeyed & Partial<{ to: string | number; to_as_string: string; }> & { doc_count: number; key: string; }, string>; derivative: { value: number | null; } | undefined; extended_stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_of_squares_as_string: string; variance_population_as_string: string; variance_sampling_as_string: string; std_deviation_as_string: string; std_deviation_population_as_string: string; std_deviation_sampling_as_string: string; std_deviation_bounds_as_string: { upper: string; lower: string; upper_population: string; lower_population: string; upper_sampling: string; lower_sampling: string; }; }); extended_stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number | null; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; }; filter: { doc_count: number; } & SubAggregateOf; filters: { buckets: TAggregationContainer extends { filters: { filters: any[]; }; } ? ({ doc_count: number; } & SubAggregateOf)[] : TAggregationContainer extends { filters: { filters: Record; }; } ? { [key in keyof TAggregationContainer[\"filters\"][\"filters\"]]: { doc_count: number; } & SubAggregateOf; } & (TAggregationContainer extends { filters: { other_bucket_key: infer TOtherBucketKey; }; } ? Record> : unknown) & (TAggregationContainer extends { filters: { other_bucket: true; }; } ? { _other: { doc_count: number; } & SubAggregateOf; } : unknown) : unknown; }; geo_bounds: { top_left: { lat: number | null; lon: number | null; }; bottom_right: { lat: number | null; lon: number | null; }; }; geo_centroid: { count: number; location: { lat: number; lon: number; }; }; geo_distance: MaybeKeyed, string>; geo_hash: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; geotile_grid: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; global: { doc_count: number; } & SubAggregateOf; histogram: MaybeKeyed, string>; ip_range: MaybeKeyed; inference: { value: number; prediction_probability: number; prediction_score: number; }; max: { value: number | null; value_as_string?: string | undefined; }; max_bucket: { value: number | null; }; min: { value: number | null; value_as_string?: string | undefined; }; min_bucket: { value: number | null; }; median_absolute_deviation: { value: number | null; }; moving_avg: { value: number | null; } | undefined; moving_fn: { value: number | null; }; moving_percentiles: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record | undefined; missing: { doc_count: number; } & SubAggregateOf; multi_terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string[]; } & SubAggregateOf)[]; }; nested: { doc_count: number; } & SubAggregateOf; normalize: { value: number | null; value_as_string?: string | undefined; }; parent: { doc_count: number; } & SubAggregateOf; percentiles: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentile_ranks: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentiles_bucket: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; range: MaybeKeyed; rare_terms: ({ key: string | number; doc_count: number; } & SubAggregateOf)[]; rate: { value: number | null; }; reverse_nested: { doc_count: number; } & SubAggregateOf; random_sampler: { seed: number; probability: number; doc_count: number; } & SubAggregateOf; sampler: { doc_count: number; } & SubAggregateOf; scripted_metric: { value: unknown; }; serial_diff: { value: number | null; value_as_string?: string | undefined; }; significant_terms: { doc_count: number; bg_count: number; buckets: ({ key: string | number; score: number; doc_count: number; bg_count: number; } & SubAggregateOf)[]; }; significant_text: { doc_count: number; buckets: { key: string; doc_count: number; score: number; bg_count: number; }[]; }; stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_as_string: string; }); stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; }; string_stats: { count: number; min_length: number | null; max_length: number | null; avg_length: number | null; entropy: number | null; distribution: Record; }; sum: { value: number | null; value_as_string?: string | undefined; }; sum_bucket: { value: number | null; }; terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string | number; key_as_string?: string | undefined; } & SubAggregateOf)[]; }; top_hits: { hits: { total: { value: number; relation: \"gte\" | \"eq\"; }; max_score: number | null; hits: TAggregationContainer extends { top_hits: ", + "AggregationsTopHitsAggregation", + "; } ? HitsOf : ", + "SearchHitsMetadata", + "; }; }; top_metrics: { top: { sort: string[] | number[]; metrics: Record, string | number | null>; }[]; }; weighted_avg: { value: number | null; }; value_count: { value: number; }; }, Exclude, \"aggs\" | \"aggregations\"> & string> extends readonly any[] ? (readonly any[] & Pick & { adjacency_matrix: { buckets: ({ key: string; doc_count: number; } & SubAggregateOf)[]; }; auto_date_histogram: { interval: string; buckets: ({ key: number; key_as_string: string; doc_count: number; } & SubAggregateOf)[]; }; avg: { value: number | null; value_as_string?: string | undefined; }; avg_bucket: { value: number | null; }; boxplot: { min: number | null; max: number | null; q1: number | null; q2: number | null; q3: number | null; }; bucket_correlation: { value: number | null; }; bucket_count_ks_test: { less: number; greater: number; two_sided: number; }; bucket_script: { value: unknown; }; cardinality: { value: number; }; children: { doc_count: number; } & SubAggregateOf; composite: { after_key: CompositeKeysOf; buckets: ({ doc_count: number; key: CompositeKeysOf; } & SubAggregateOf)[]; }; cumulative_cardinality: { value: number; }; cumulative_sum: { value: number; }; date_histogram: MaybeKeyed, string>; date_range: MaybeKeyed & Partial<{ to: string | number; to_as_string: string; }> & { doc_count: number; key: string; }, string>; derivative: { value: number | null; } | undefined; extended_stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_of_squares_as_string: string; variance_population_as_string: string; variance_sampling_as_string: string; std_deviation_as_string: string; std_deviation_population_as_string: string; std_deviation_sampling_as_string: string; std_deviation_bounds_as_string: { upper: string; lower: string; upper_population: string; lower_population: string; upper_sampling: string; lower_sampling: string; }; }); extended_stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number | null; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; }; filter: { doc_count: number; } & SubAggregateOf; filters: { buckets: TAggregationContainer extends { filters: { filters: any[]; }; } ? ({ doc_count: number; } & SubAggregateOf)[] : TAggregationContainer extends { filters: { filters: Record; }; } ? { [key in keyof TAggregationContainer[\"filters\"][\"filters\"]]: { doc_count: number; } & SubAggregateOf; } & (TAggregationContainer extends { filters: { other_bucket_key: infer TOtherBucketKey; }; } ? Record> : unknown) & (TAggregationContainer extends { filters: { other_bucket: true; }; } ? { _other: { doc_count: number; } & SubAggregateOf; } : unknown) : unknown; }; geo_bounds: { top_left: { lat: number | null; lon: number | null; }; bottom_right: { lat: number | null; lon: number | null; }; }; geo_centroid: { count: number; location: { lat: number; lon: number; }; }; geo_distance: MaybeKeyed, string>; geo_hash: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; geotile_grid: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; global: { doc_count: number; } & SubAggregateOf; histogram: MaybeKeyed, string>; ip_range: MaybeKeyed; inference: { value: number; prediction_probability: number; prediction_score: number; }; max: { value: number | null; value_as_string?: string | undefined; }; max_bucket: { value: number | null; }; min: { value: number | null; value_as_string?: string | undefined; }; min_bucket: { value: number | null; }; median_absolute_deviation: { value: number | null; }; moving_avg: { value: number | null; } | undefined; moving_fn: { value: number | null; }; moving_percentiles: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record | undefined; missing: { doc_count: number; } & SubAggregateOf; multi_terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string[]; } & SubAggregateOf)[]; }; nested: { doc_count: number; } & SubAggregateOf; normalize: { value: number | null; value_as_string?: string | undefined; }; parent: { doc_count: number; } & SubAggregateOf; percentiles: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentile_ranks: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentiles_bucket: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; range: MaybeKeyed; rare_terms: ({ key: string | number; doc_count: number; } & SubAggregateOf)[]; rate: { value: number | null; }; reverse_nested: { doc_count: number; } & SubAggregateOf; random_sampler: { seed: number; probability: number; doc_count: number; } & SubAggregateOf; sampler: { doc_count: number; } & SubAggregateOf; scripted_metric: { value: unknown; }; serial_diff: { value: number | null; value_as_string?: string | undefined; }; significant_terms: { doc_count: number; bg_count: number; buckets: ({ key: string | number; score: number; doc_count: number; bg_count: number; } & SubAggregateOf)[]; }; significant_text: { doc_count: number; buckets: { key: string; doc_count: number; score: number; bg_count: number; }[]; }; stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_as_string: string; }); stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; }; string_stats: { count: number; min_length: number | null; max_length: number | null; avg_length: number | null; entropy: number | null; distribution: Record; }; sum: { value: number | null; value_as_string?: string | undefined; }; sum_bucket: { value: number | null; }; terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string | number; key_as_string?: string | undefined; } & SubAggregateOf)[]; }; top_hits: { hits: { total: { value: number; relation: \"gte\" | \"eq\"; }; max_score: number | null; hits: TAggregationContainer extends { top_hits: ", + "AggregationsTopHitsAggregation", + "; } ? HitsOf : ", + "SearchHitsMetadata", + "; }; }; top_metrics: { top: { sort: string[] | number[]; metrics: Record, string | number | null>; }[]; }; weighted_avg: { value: number | null; }; value_count: { value: number; }; }, Exclude, \"aggs\" | \"aggregations\"> & string>)[number] : Pick & { adjacency_matrix: { buckets: ({ key: string; doc_count: number; } & SubAggregateOf)[]; }; auto_date_histogram: { interval: string; buckets: ({ key: number; key_as_string: string; doc_count: number; } & SubAggregateOf)[]; }; avg: { value: number | null; value_as_string?: string | undefined; }; avg_bucket: { value: number | null; }; boxplot: { min: number | null; max: number | null; q1: number | null; q2: number | null; q3: number | null; }; bucket_correlation: { value: number | null; }; bucket_count_ks_test: { less: number; greater: number; two_sided: number; }; bucket_script: { value: unknown; }; cardinality: { value: number; }; children: { doc_count: number; } & SubAggregateOf; composite: { after_key: CompositeKeysOf; buckets: ({ doc_count: number; key: CompositeKeysOf; } & SubAggregateOf)[]; }; cumulative_cardinality: { value: number; }; cumulative_sum: { value: number; }; date_histogram: MaybeKeyed, string>; date_range: MaybeKeyed & Partial<{ to: string | number; to_as_string: string; }> & { doc_count: number; key: string; }, string>; derivative: { value: number | null; } | undefined; extended_stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_of_squares_as_string: string; variance_population_as_string: string; variance_sampling_as_string: string; std_deviation_as_string: string; std_deviation_population_as_string: string; std_deviation_sampling_as_string: string; std_deviation_bounds_as_string: { upper: string; lower: string; upper_population: string; lower_population: string; upper_sampling: string; lower_sampling: string; }; }); extended_stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number | null; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; }; filter: { doc_count: number; } & SubAggregateOf; filters: { buckets: TAggregationContainer extends { filters: { filters: any[]; }; } ? ({ doc_count: number; } & SubAggregateOf)[] : TAggregationContainer extends { filters: { filters: Record; }; } ? { [key in keyof TAggregationContainer[\"filters\"][\"filters\"]]: { doc_count: number; } & SubAggregateOf; } & (TAggregationContainer extends { filters: { other_bucket_key: infer TOtherBucketKey; }; } ? Record> : unknown) & (TAggregationContainer extends { filters: { other_bucket: true; }; } ? { _other: { doc_count: number; } & SubAggregateOf; } : unknown) : unknown; }; geo_bounds: { top_left: { lat: number | null; lon: number | null; }; bottom_right: { lat: number | null; lon: number | null; }; }; geo_centroid: { count: number; location: { lat: number; lon: number; }; }; geo_distance: MaybeKeyed, string>; geo_hash: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; geotile_grid: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; global: { doc_count: number; } & SubAggregateOf; histogram: MaybeKeyed, string>; ip_range: MaybeKeyed; inference: { value: number; prediction_probability: number; prediction_score: number; }; max: { value: number | null; value_as_string?: string | undefined; }; max_bucket: { value: number | null; }; min: { value: number | null; value_as_string?: string | undefined; }; min_bucket: { value: number | null; }; median_absolute_deviation: { value: number | null; }; moving_avg: { value: number | null; } | undefined; moving_fn: { value: number | null; }; moving_percentiles: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record | undefined; missing: { doc_count: number; } & SubAggregateOf; multi_terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string[]; } & SubAggregateOf)[]; }; nested: { doc_count: number; } & SubAggregateOf; normalize: { value: number | null; value_as_string?: string | undefined; }; parent: { doc_count: number; } & SubAggregateOf; percentiles: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentile_ranks: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentiles_bucket: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; range: MaybeKeyed; rare_terms: ({ key: string | number; doc_count: number; } & SubAggregateOf)[]; rate: { value: number | null; }; reverse_nested: { doc_count: number; } & SubAggregateOf; random_sampler: { seed: number; probability: number; doc_count: number; } & SubAggregateOf; sampler: { doc_count: number; } & SubAggregateOf; scripted_metric: { value: unknown; }; serial_diff: { value: number | null; value_as_string?: string | undefined; }; significant_terms: { doc_count: number; bg_count: number; buckets: ({ key: string | number; score: number; doc_count: number; bg_count: number; } & SubAggregateOf)[]; }; significant_text: { doc_count: number; buckets: { key: string; doc_count: number; score: number; bg_count: number; }[]; }; stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_as_string: string; }); stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; }; string_stats: { count: number; min_length: number | null; max_length: number | null; avg_length: number | null; entropy: number | null; distribution: Record; }; sum: { value: number | null; value_as_string?: string | undefined; }; sum_bucket: { value: number | null; }; terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string | number; key_as_string?: string | undefined; } & SubAggregateOf)[]; }; top_hits: { hits: { total: { value: number; relation: \"gte\" | \"eq\"; }; max_score: number | null; hits: TAggregationContainer extends { top_hits: ", + "AggregationsTopHitsAggregation", + "; } ? HitsOf : ", + "SearchHitsMetadata", + "; }; }; top_metrics: { top: { sort: string[] | number[]; metrics: Record, string | number | null>; }[]; }; weighted_avg: { value: number | null; }; value_count: { value: number; }; }, Exclude, \"aggs\" | \"aggregations\"> & string> extends ArrayLike ? (ArrayLike & Pick & { adjacency_matrix: { buckets: ({ key: string; doc_count: number; } & SubAggregateOf)[]; }; auto_date_histogram: { interval: string; buckets: ({ key: number; key_as_string: string; doc_count: number; } & SubAggregateOf)[]; }; avg: { value: number | null; value_as_string?: string | undefined; }; avg_bucket: { value: number | null; }; boxplot: { min: number | null; max: number | null; q1: number | null; q2: number | null; q3: number | null; }; bucket_correlation: { value: number | null; }; bucket_count_ks_test: { less: number; greater: number; two_sided: number; }; bucket_script: { value: unknown; }; cardinality: { value: number; }; children: { doc_count: number; } & SubAggregateOf; composite: { after_key: CompositeKeysOf; buckets: ({ doc_count: number; key: CompositeKeysOf; } & SubAggregateOf)[]; }; cumulative_cardinality: { value: number; }; cumulative_sum: { value: number; }; date_histogram: MaybeKeyed, string>; date_range: MaybeKeyed & Partial<{ to: string | number; to_as_string: string; }> & { doc_count: number; key: string; }, string>; derivative: { value: number | null; } | undefined; extended_stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_of_squares_as_string: string; variance_population_as_string: string; variance_sampling_as_string: string; std_deviation_as_string: string; std_deviation_population_as_string: string; std_deviation_sampling_as_string: string; std_deviation_bounds_as_string: { upper: string; lower: string; upper_population: string; lower_population: string; upper_sampling: string; lower_sampling: string; }; }); extended_stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number | null; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; }; filter: { doc_count: number; } & SubAggregateOf; filters: { buckets: TAggregationContainer extends { filters: { filters: any[]; }; } ? ({ doc_count: number; } & SubAggregateOf)[] : TAggregationContainer extends { filters: { filters: Record; }; } ? { [key in keyof TAggregationContainer[\"filters\"][\"filters\"]]: { doc_count: number; } & SubAggregateOf; } & (TAggregationContainer extends { filters: { other_bucket_key: infer TOtherBucketKey; }; } ? Record> : unknown) & (TAggregationContainer extends { filters: { other_bucket: true; }; } ? { _other: { doc_count: number; } & SubAggregateOf; } : unknown) : unknown; }; geo_bounds: { top_left: { lat: number | null; lon: number | null; }; bottom_right: { lat: number | null; lon: number | null; }; }; geo_centroid: { count: number; location: { lat: number; lon: number; }; }; geo_distance: MaybeKeyed, string>; geo_hash: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; geotile_grid: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; global: { doc_count: number; } & SubAggregateOf; histogram: MaybeKeyed, string>; ip_range: MaybeKeyed; inference: { value: number; prediction_probability: number; prediction_score: number; }; max: { value: number | null; value_as_string?: string | undefined; }; max_bucket: { value: number | null; }; min: { value: number | null; value_as_string?: string | undefined; }; min_bucket: { value: number | null; }; median_absolute_deviation: { value: number | null; }; moving_avg: { value: number | null; } | undefined; moving_fn: { value: number | null; }; moving_percentiles: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record | undefined; missing: { doc_count: number; } & SubAggregateOf; multi_terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string[]; } & SubAggregateOf)[]; }; nested: { doc_count: number; } & SubAggregateOf; normalize: { value: number | null; value_as_string?: string | undefined; }; parent: { doc_count: number; } & SubAggregateOf; percentiles: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentile_ranks: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentiles_bucket: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; range: MaybeKeyed; rare_terms: ({ key: string | number; doc_count: number; } & SubAggregateOf)[]; rate: { value: number | null; }; reverse_nested: { doc_count: number; } & SubAggregateOf; random_sampler: { seed: number; probability: number; doc_count: number; } & SubAggregateOf; sampler: { doc_count: number; } & SubAggregateOf; scripted_metric: { value: unknown; }; serial_diff: { value: number | null; value_as_string?: string | undefined; }; significant_terms: { doc_count: number; bg_count: number; buckets: ({ key: string | number; score: number; doc_count: number; bg_count: number; } & SubAggregateOf)[]; }; significant_text: { doc_count: number; buckets: { key: string; doc_count: number; score: number; bg_count: number; }[]; }; stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_as_string: string; }); stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; }; string_stats: { count: number; min_length: number | null; max_length: number | null; avg_length: number | null; entropy: number | null; distribution: Record; }; sum: { value: number | null; value_as_string?: string | undefined; }; sum_bucket: { value: number | null; }; terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string | number; key_as_string?: string | undefined; } & SubAggregateOf)[]; }; top_hits: { hits: { total: { value: number; relation: \"gte\" | \"eq\"; }; max_score: number | null; hits: TAggregationContainer extends { top_hits: ", + "AggregationsTopHitsAggregation", + "; } ? HitsOf : ", + "SearchHitsMetadata", + "; }; }; top_metrics: { top: { sort: string[] | number[]; metrics: Record, string | number | null>; }[]; }; weighted_avg: { value: number | null; }; value_count: { value: number; }; }, Exclude, \"aggs\" | \"aggregations\"> & string>)[number] : Pick & { adjacency_matrix: { buckets: ({ key: string; doc_count: number; } & SubAggregateOf)[]; }; auto_date_histogram: { interval: string; buckets: ({ key: number; key_as_string: string; doc_count: number; } & SubAggregateOf)[]; }; avg: { value: number | null; value_as_string?: string | undefined; }; avg_bucket: { value: number | null; }; boxplot: { min: number | null; max: number | null; q1: number | null; q2: number | null; q3: number | null; }; bucket_correlation: { value: number | null; }; bucket_count_ks_test: { less: number; greater: number; two_sided: number; }; bucket_script: { value: unknown; }; cardinality: { value: number; }; children: { doc_count: number; } & SubAggregateOf; composite: { after_key: CompositeKeysOf; buckets: ({ doc_count: number; key: CompositeKeysOf; } & SubAggregateOf)[]; }; cumulative_cardinality: { value: number; }; cumulative_sum: { value: number; }; date_histogram: MaybeKeyed, string>; date_range: MaybeKeyed & Partial<{ to: string | number; to_as_string: string; }> & { doc_count: number; key: string; }, string>; derivative: { value: number | null; } | undefined; extended_stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_of_squares_as_string: string; variance_population_as_string: string; variance_sampling_as_string: string; std_deviation_as_string: string; std_deviation_population_as_string: string; std_deviation_sampling_as_string: string; std_deviation_bounds_as_string: { upper: string; lower: string; upper_population: string; lower_population: string; upper_sampling: string; lower_sampling: string; }; }); extended_stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number | null; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; }; filter: { doc_count: number; } & SubAggregateOf; filters: { buckets: TAggregationContainer extends { filters: { filters: any[]; }; } ? ({ doc_count: number; } & SubAggregateOf)[] : TAggregationContainer extends { filters: { filters: Record; }; } ? { [key in keyof TAggregationContainer[\"filters\"][\"filters\"]]: { doc_count: number; } & SubAggregateOf; } & (TAggregationContainer extends { filters: { other_bucket_key: infer TOtherBucketKey; }; } ? Record> : unknown) & (TAggregationContainer extends { filters: { other_bucket: true; }; } ? { _other: { doc_count: number; } & SubAggregateOf; } : unknown) : unknown; }; geo_bounds: { top_left: { lat: number | null; lon: number | null; }; bottom_right: { lat: number | null; lon: number | null; }; }; geo_centroid: { count: number; location: { lat: number; lon: number; }; }; geo_distance: MaybeKeyed, string>; geo_hash: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; geotile_grid: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; global: { doc_count: number; } & SubAggregateOf; histogram: MaybeKeyed, string>; ip_range: MaybeKeyed; inference: { value: number; prediction_probability: number; prediction_score: number; }; max: { value: number | null; value_as_string?: string | undefined; }; max_bucket: { value: number | null; }; min: { value: number | null; value_as_string?: string | undefined; }; min_bucket: { value: number | null; }; median_absolute_deviation: { value: number | null; }; moving_avg: { value: number | null; } | undefined; moving_fn: { value: number | null; }; moving_percentiles: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record | undefined; missing: { doc_count: number; } & SubAggregateOf; multi_terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string[]; } & SubAggregateOf)[]; }; nested: { doc_count: number; } & SubAggregateOf; normalize: { value: number | null; value_as_string?: string | undefined; }; parent: { doc_count: number; } & SubAggregateOf; percentiles: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentile_ranks: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentiles_bucket: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; range: MaybeKeyed; rare_terms: ({ key: string | number; doc_count: number; } & SubAggregateOf)[]; rate: { value: number | null; }; reverse_nested: { doc_count: number; } & SubAggregateOf; random_sampler: { seed: number; probability: number; doc_count: number; } & SubAggregateOf; sampler: { doc_count: number; } & SubAggregateOf; scripted_metric: { value: unknown; }; serial_diff: { value: number | null; value_as_string?: string | undefined; }; significant_terms: { doc_count: number; bg_count: number; buckets: ({ key: string | number; score: number; doc_count: number; bg_count: number; } & SubAggregateOf)[]; }; significant_text: { doc_count: number; buckets: { key: string; doc_count: number; score: number; bg_count: number; }[]; }; stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_as_string: string; }); stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; }; string_stats: { count: number; min_length: number | null; max_length: number | null; avg_length: number | null; entropy: number | null; distribution: Record; }; sum: { value: number | null; value_as_string?: string | undefined; }; sum_bucket: { value: number | null; }; terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string | number; key_as_string?: string | undefined; } & SubAggregateOf)[]; }; top_hits: { hits: { total: { value: number; relation: \"gte\" | \"eq\"; }; max_score: number | null; hits: TAggregationContainer extends { top_hits: ", + "AggregationsTopHitsAggregation", + "; } ? HitsOf : ", + "SearchHitsMetadata", + "; }; }; top_metrics: { top: { sort: string[] | number[]; metrics: Record, string | number | null>; }[]; }; weighted_avg: { value: number | null; }; value_count: { value: number; }; }, Exclude, \"aggs\" | \"aggregations\"> & string> extends object ? Pick & { adjacency_matrix: { buckets: ({ key: string; doc_count: number; } & SubAggregateOf)[]; }; auto_date_histogram: { interval: string; buckets: ({ key: number; key_as_string: string; doc_count: number; } & SubAggregateOf)[]; }; avg: { value: number | null; value_as_string?: string | undefined; }; avg_bucket: { value: number | null; }; boxplot: { min: number | null; max: number | null; q1: number | null; q2: number | null; q3: number | null; }; bucket_correlation: { value: number | null; }; bucket_count_ks_test: { less: number; greater: number; two_sided: number; }; bucket_script: { value: unknown; }; cardinality: { value: number; }; children: { doc_count: number; } & SubAggregateOf; composite: { after_key: CompositeKeysOf; buckets: ({ doc_count: number; key: CompositeKeysOf; } & SubAggregateOf)[]; }; cumulative_cardinality: { value: number; }; cumulative_sum: { value: number; }; date_histogram: MaybeKeyed, string>; date_range: MaybeKeyed & Partial<{ to: string | number; to_as_string: string; }> & { doc_count: number; key: string; }, string>; derivative: { value: number | null; } | undefined; extended_stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_of_squares_as_string: string; variance_population_as_string: string; variance_sampling_as_string: string; std_deviation_as_string: string; std_deviation_population_as_string: string; std_deviation_sampling_as_string: string; std_deviation_bounds_as_string: { upper: string; lower: string; upper_population: string; lower_population: string; upper_sampling: string; lower_sampling: string; }; }); extended_stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number | null; sum_of_squares: number | null; variance: number | null; variance_population: number | null; variance_sampling: number | null; std_deviation: number | null; std_deviation_population: number | null; std_deviation_sampling: number | null; std_deviation_bounds: { upper: number | null; lower: number | null; upper_population: number | null; lower_population: number | null; upper_sampling: number | null; lower_sampling: number | null; }; }; filter: { doc_count: number; } & SubAggregateOf; filters: { buckets: TAggregationContainer extends { filters: { filters: any[]; }; } ? ({ doc_count: number; } & SubAggregateOf)[] : TAggregationContainer extends { filters: { filters: Record; }; } ? { [key in keyof TAggregationContainer[\"filters\"][\"filters\"]]: { doc_count: number; } & SubAggregateOf; } & (TAggregationContainer extends { filters: { other_bucket_key: infer TOtherBucketKey; }; } ? Record> : unknown) & (TAggregationContainer extends { filters: { other_bucket: true; }; } ? { _other: { doc_count: number; } & SubAggregateOf; } : unknown) : unknown; }; geo_bounds: { top_left: { lat: number | null; lon: number | null; }; bottom_right: { lat: number | null; lon: number | null; }; }; geo_centroid: { count: number; location: { lat: number; lon: number; }; }; geo_distance: MaybeKeyed, string>; geo_hash: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; geotile_grid: { buckets: ({ doc_count: number; key: string; } & SubAggregateOf)[]; }; global: { doc_count: number; } & SubAggregateOf; histogram: MaybeKeyed, string>; ip_range: MaybeKeyed; inference: { value: number; prediction_probability: number; prediction_score: number; }; max: { value: number | null; value_as_string?: string | undefined; }; max_bucket: { value: number | null; }; min: { value: number | null; value_as_string?: string | undefined; }; min_bucket: { value: number | null; }; median_absolute_deviation: { value: number | null; }; moving_avg: { value: number | null; } | undefined; moving_fn: { value: number | null; }; moving_percentiles: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record | undefined; missing: { doc_count: number; } & SubAggregateOf; multi_terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string[]; } & SubAggregateOf)[]; }; nested: { doc_count: number; } & SubAggregateOf; normalize: { value: number | null; value_as_string?: string | undefined; }; parent: { doc_count: number; } & SubAggregateOf; percentiles: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentile_ranks: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; percentiles_bucket: { values: TAggregationContainer extends Record ? { key: number; value: number | null; }[] : Record; }; range: MaybeKeyed; rare_terms: ({ key: string | number; doc_count: number; } & SubAggregateOf)[]; rate: { value: number | null; }; reverse_nested: { doc_count: number; } & SubAggregateOf; random_sampler: { seed: number; probability: number; doc_count: number; } & SubAggregateOf; sampler: { doc_count: number; } & SubAggregateOf; scripted_metric: { value: unknown; }; serial_diff: { value: number | null; value_as_string?: string | undefined; }; significant_terms: { doc_count: number; bg_count: number; buckets: ({ key: string | number; score: number; doc_count: number; bg_count: number; } & SubAggregateOf)[]; }; significant_text: { doc_count: number; buckets: { key: string; doc_count: number; score: number; bg_count: number; }[]; }; stats: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; } & ({} | { min_as_string: string; max_as_string: string; avg_as_string: string; sum_as_string: string; }); stats_bucket: { count: number; min: number | null; max: number | null; avg: number | null; sum: number; }; string_stats: { count: number; min_length: number | null; max_length: number | null; avg_length: number | null; entropy: number | null; distribution: Record; }; sum: { value: number | null; value_as_string?: string | undefined; }; sum_bucket: { value: number | null; }; terms: { doc_count_error_upper_bound: number; sum_other_doc_count: number; buckets: ({ doc_count: number; key: string | number; key_as_string?: string | undefined; } & SubAggregateOf)[]; }; top_hits: { hits: { total: { value: number; relation: \"gte\" | \"eq\"; }; max_score: number | null; hits: TAggregationContainer extends { top_hits: ", + "AggregationsTopHitsAggregation", + "; } ? HitsOf : ", + "SearchHitsMetadata", + "; }; }; top_metrics: { top: { sort: string[] | number[]; metrics: Record, string | number | null>; }[]; }; weighted_avg: { value: number | null; }; value_count: { value: number; }; }, Exclude, \"aggs\" | \"aggregations\"> & string>[Exclude, \"aggs\" | \"aggregations\"> & string] : never" + ], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.AggregationOptionsByType", + "type": "Type", + "tags": [], + "label": "AggregationOptionsByType", + "description": [], + "signature": [ + "{ aggregations: Record; aggs: Record; meta: ", + "Metadata", + "; adjacency_matrix: ", + "AggregationsAdjacencyMatrixAggregation", + "; auto_date_histogram: ", + "AggregationsAutoDateHistogramAggregation", + "; avg: ", + "AggregationsAverageAggregation", + "; avg_bucket: ", + "AggregationsAverageBucketAggregation", + "; boxplot: ", + "AggregationsBoxplotAggregation", + "; bucket_script: ", + "AggregationsBucketScriptAggregation", + "; bucket_selector: ", + "AggregationsBucketSelectorAggregation", + "; bucket_sort: ", + "AggregationsBucketSortAggregation", + "; bucket_count_ks_test: ", + "AggregationsBucketKsAggregation", + "; bucket_correlation: ", + "AggregationsBucketCorrelationAggregation", + "; cardinality: ", + "AggregationsCardinalityAggregation", + "; categorize_text: ", + "AggregationsCategorizeTextAggregation", + "; children: ", + "AggregationsChildrenAggregation", + "; composite: ", + "AggregationsCompositeAggregation", + "; cumulative_cardinality: ", + "AggregationsCumulativeCardinalityAggregation", + "; cumulative_sum: ", + "AggregationsCumulativeSumAggregation", + "; date_histogram: ", + "AggregationsDateHistogramAggregation", + "; date_range: ", + "AggregationsDateRangeAggregation", + "; derivative: ", + "AggregationsDerivativeAggregation", + "; diversified_sampler: ", + "AggregationsDiversifiedSamplerAggregation", + "; extended_stats: ", + "AggregationsExtendedStatsAggregation", + "; extended_stats_bucket: ", + "AggregationsExtendedStatsBucketAggregation", + "; filter: ", + "QueryDslQueryContainer", + "; filters: ", + "AggregationsFiltersAggregation", + "; geo_bounds: ", + "AggregationsGeoBoundsAggregation", + "; geo_centroid: ", + "AggregationsGeoCentroidAggregation", + "; geo_distance: ", + "AggregationsGeoDistanceAggregation", + "; geohash_grid: ", + "AggregationsGeoHashGridAggregation", + "; geo_line: ", + "AggregationsGeoLineAggregation", + "; geotile_grid: ", + "AggregationsGeoTileGridAggregation", + "; geohex_grid: ", + "AggregationsGeohexGridAggregation", + "; global: ", + "AggregationsGlobalAggregation", + "; histogram: ", + "AggregationsHistogramAggregation", + "; ip_range: ", + "AggregationsIpRangeAggregation", + "; inference: ", + "AggregationsInferenceAggregation", + "; line: ", + "AggregationsGeoLineAggregation", + "; matrix_stats: ", + "AggregationsMatrixStatsAggregation", + "; max: ", + "AggregationsMaxAggregation", + "; max_bucket: ", + "AggregationsMaxBucketAggregation", + "; median_absolute_deviation: ", + "AggregationsMedianAbsoluteDeviationAggregation", + "; min: ", + "AggregationsMinAggregation", + "; min_bucket: ", + "AggregationsMinBucketAggregation", + "; missing: ", + "AggregationsMissingAggregation", + "; moving_avg: ", + "AggregationsMovingAverageAggregation", + "; moving_percentiles: ", + "AggregationsMovingPercentilesAggregation", + "; moving_fn: ", + "AggregationsMovingFunctionAggregation", + "; multi_terms: ", + "AggregationsMultiTermsAggregation", + "; nested: ", + "AggregationsNestedAggregation", + "; normalize: ", + "AggregationsNormalizeAggregation", + "; parent: ", + "AggregationsParentAggregation", + "; percentile_ranks: ", + "AggregationsPercentileRanksAggregation", + "; percentiles: ", + "AggregationsPercentilesAggregation", + "; percentiles_bucket: ", + "AggregationsPercentilesBucketAggregation", + "; range: ", + "AggregationsRangeAggregation", + "; rare_terms: ", + "AggregationsRareTermsAggregation", + "; rate: ", + "AggregationsRateAggregation", + "; reverse_nested: ", + "AggregationsReverseNestedAggregation", + "; sampler: ", + "AggregationsSamplerAggregation", + "; scripted_metric: ", + "AggregationsScriptedMetricAggregation", + "; serial_diff: ", + "AggregationsSerialDifferencingAggregation", + "; significant_terms: ", + "AggregationsSignificantTermsAggregation", + "; significant_text: ", + "AggregationsSignificantTextAggregation", + "; stats: ", + "AggregationsStatsAggregation", + "; stats_bucket: ", + "AggregationsStatsBucketAggregation", + "; string_stats: ", + "AggregationsStringStatsAggregation", + "; sum: ", + "AggregationsSumAggregation", + "; sum_bucket: ", + "AggregationsSumBucketAggregation", + "; terms: ", + "AggregationsTermsAggregation", + "; top_hits: ", + "AggregationsTopHitsAggregation", + "; t_test: ", + "AggregationsTTestAggregation", + "; top_metrics: ", + "AggregationsTopMetricsAggregation", + "; value_count: ", + "AggregationsValueCountAggregation", + "; weighted_avg: ", + "AggregationsWeightedAverageAggregation", + "; variable_width_histogram: ", + "AggregationsVariableWidthHistogramAggregation", + "; }" + ], + "path": "packages/kbn-es-types/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.ESFilter", + "type": "Type", + "tags": [], + "label": "ESFilter", + "description": [], + "signature": [ + "QueryDslQueryContainer" + ], + "path": "packages/kbn-es-types/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.ESSearchRequest", + "type": "Type", + "tags": [], + "label": "ESSearchRequest", + "description": [], + "signature": [ + "SearchRequest" + ], + "path": "packages/kbn-es-types/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.ESSearchResponse", + "type": "Type", + "tags": [], + "label": "ESSearchResponse", + "description": [], + "signature": [ + "Omit<", + "SearchResponse", + ">, \"hits\" | \"aggregations\"> & (TSearchRequest[\"body\"] extends TopLevelAggregationRequest ? WrapAggregationResponse> : TSearchRequest extends TopLevelAggregationRequest ? WrapAggregationResponse> : { aggregations?: unknown; }) & { hits: Omit<", + "SearchHitsMetadata", + ", \"hits\" | \"total\"> & (TOptions[\"restTotalHitsAsInt\"] extends true ? { total: number; } : { total: { value: number; relation: \"gte\" | \"eq\"; }; }) & { hits: HitsOf | undefined; aggs?: Record | undefined; collapse?: ", + "SearchFieldCollapse", + " | undefined; explain?: boolean | undefined; from?: number | undefined; highlight?: ", + "SearchHighlight", + " | undefined; track_total_hits?: ", + "SearchTrackHits", + " | undefined; indices_boost?: Record[] | undefined; docvalue_fields?: (string | ", + "QueryDslFieldAndFormat", + ")[] | undefined; min_score?: number | undefined; post_filter?: ", + "QueryDslQueryContainer", + " | undefined; profile?: boolean | undefined; query?: ", + "QueryDslQueryContainer", + " | undefined; rescore?: ", + "SearchRescore", + " | ", + "SearchRescore", + "[] | undefined; script_fields?: Record | undefined; search_after?: ", + "SortResults", + " | undefined; size?: number | undefined; slice?: ", + "SlicedScroll", + " | undefined; sort?: ", + "Sort", + " | undefined; _source?: ", + "SearchSourceConfig", + " | undefined; fields?: (string | ", + "QueryDslFieldAndFormat", + ")[] | undefined; suggest?: ", + "SearchSuggester", + " | undefined; terminate_after?: number | undefined; timeout?: string | undefined; track_scores?: boolean | undefined; version?: boolean | undefined; seq_no_primary_term?: boolean | undefined; stored_fields?: ", + "Fields", + " | undefined; pit?: ", + "SearchPointInTimeReference", + " | undefined; runtime_mappings?: ", + "MappingRuntimeFields", + " | undefined; stats?: string[] | undefined; } | undefined ? TSearchRequest[\"body\"] : TSearchRequest, TDocument>; }; }" + ], + "path": "packages/kbn-es-types/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.ESSourceOptions", + "type": "Type", + "tags": [], + "label": "ESSourceOptions", + "description": [], + "signature": [ + "string | boolean | string[]" + ], + "path": "packages/kbn-es-types/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.InferSearchResponseOf", + "type": "Type", + "tags": [], + "label": "InferSearchResponseOf", + "description": [], + "signature": [ + "Omit<", + "SearchResponse", + ">, \"hits\" | \"aggregations\"> & (TSearchRequest[\"body\"] extends TopLevelAggregationRequest ? WrapAggregationResponse> : TSearchRequest extends TopLevelAggregationRequest ? WrapAggregationResponse> : { aggregations?: unknown; }) & { hits: Omit<", + "SearchHitsMetadata", + ", \"hits\" | \"total\"> & (TOptions[\"restTotalHitsAsInt\"] extends true ? { total: number; } : { total: { value: number; relation: \"gte\" | \"eq\"; }; }) & { hits: HitsOf | undefined; aggs?: Record | undefined; collapse?: ", + "SearchFieldCollapse", + " | undefined; explain?: boolean | undefined; from?: number | undefined; highlight?: ", + "SearchHighlight", + " | undefined; track_total_hits?: ", + "SearchTrackHits", + " | undefined; indices_boost?: Record[] | undefined; docvalue_fields?: (string | ", + "QueryDslFieldAndFormat", + ")[] | undefined; min_score?: number | undefined; post_filter?: ", + "QueryDslQueryContainer", + " | undefined; profile?: boolean | undefined; query?: ", + "QueryDslQueryContainer", + " | undefined; rescore?: ", + "SearchRescore", + " | ", + "SearchRescore", + "[] | undefined; script_fields?: Record | undefined; search_after?: ", + "SortResults", + " | undefined; size?: number | undefined; slice?: ", + "SlicedScroll", + " | undefined; sort?: ", + "Sort", + " | undefined; _source?: ", + "SearchSourceConfig", + " | undefined; fields?: (string | ", + "QueryDslFieldAndFormat", + ")[] | undefined; suggest?: ", + "SearchSuggester", + " | undefined; terminate_after?: number | undefined; timeout?: string | undefined; track_scores?: boolean | undefined; version?: boolean | undefined; seq_no_primary_term?: boolean | undefined; stored_fields?: ", + "Fields", + " | undefined; pit?: ", + "SearchPointInTimeReference", + " | undefined; runtime_mappings?: ", + "MappingRuntimeFields", + " | undefined; stats?: string[] | undefined; } | undefined ? TSearchRequest[\"body\"] : TSearchRequest, TDocument>; }; }" + ], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.MaybeReadonlyArray", + "type": "Type", + "tags": [], + "label": "MaybeReadonlyArray", + "description": [], + "signature": [ + "T[] | readonly T[]" + ], + "path": "packages/kbn-es-types/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-server.SearchHit", + "type": "Type", + "tags": [], + "label": "SearchHit", + "description": [], + "signature": [ + "Omit<", + "SearchHit", + ", \"fields\" | \"_source\"> & (TSource extends false ? {} : { _source: TSource; }) & (TFields extends (string | ", + "QueryDslFieldAndFormat", + ")[] ? { fields: Partial, unknown[]>>; } : {}) & (TDocValueFields extends DocValueFields ? { fields: Partial, unknown[]>>; } : {})" + ], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx new file mode 100644 index 0000000000000..9141040ca4c36 --- /dev/null +++ b/api_docs/kbn_es_types.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnEsTypesPluginApi +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: 2022-09-22 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] +--- +import kbnEsTypesObj from './kbn_es_types.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 11 | 0 | 11 | 0 | + +## Server + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index c555fe8b7d5a5..6ab866bd44dd1 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 0e498ea0e9b54..8310f90111603 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index eaeb3333485b0..822e97678f09b 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: 2022-09-20 +date: 2022-09-22 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_generate.mdx b/api_docs/kbn_generate.mdx index 3bcaf385b30d3..5f39a5ed34eb1 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index 32da58c287d17..3277b35b8d4a1 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index b619dcc8fce29..ef29d0fa81189 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: 2022-09-20 +date: 2022-09-22 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 6010e62895745..041357a77e22b 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 63fa535614bb5..20f96872d286b 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: 2022-09-20 +date: 2022-09-22 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 73678fded69f2..cbe688254ef8d 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: 2022-09-20 +date: 2022-09-22 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 6a2be69a99536..38dd4fd120884 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 55418c0839d6d..6e0f1031ba7a6 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index dcd0bbfddac67..940a58f4154dc 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: 2022-09-20 +date: 2022-09-22 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 2b192f2ae841e..e8b8fa5d82554 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index dfe4988fde861..fa7ba1ee6795a 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 61d0fe628c129..3181dee87f7db 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 7c2d9e7844a11..30f74050e31a9 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: 2022-09-20 +date: 2022-09-22 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 355064817f4d6..bc64637c8f1a6 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 99845433826f3..e6ead9f8174da 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 6780c1f696b69..7003f5277fc39 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.devdocs.json b/api_docs/kbn_ml_agg_utils.devdocs.json index ced4750717a78..6c021a7a5bc78 100644 --- a/api_docs/kbn_ml_agg_utils.devdocs.json +++ b/api_docs/kbn_ml_agg_utils.devdocs.json @@ -637,6 +637,17 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.ChangePointGroup.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "x-pack/packages/ml/agg_utils/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/ml-agg-utils", "id": "def-server.ChangePointGroup.group", @@ -661,6 +672,89 @@ "path": "x-pack/packages/ml/agg_utils/src/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.ChangePointGroup.pValue", + "type": "CompoundType", + "tags": [], + "label": "pValue", + "description": [], + "signature": [ + "number | null" + ], + "path": "x-pack/packages/ml/agg_utils/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.ChangePointGroup.histogram", + "type": "Array", + "tags": [], + "label": "histogram", + "description": [], + "signature": [ + { + "pluginId": "@kbn/ml-agg-utils", + "scope": "server", + "docId": "kibKbnMlAggUtilsPluginApi", + "section": "def-server.ChangePointHistogramItem", + "text": "ChangePointHistogramItem" + }, + "[] | undefined" + ], + "path": "x-pack/packages/ml/agg_utils/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.ChangePointGroupHistogram", + "type": "Interface", + "tags": [], + "label": "ChangePointGroupHistogram", + "description": [ + "\nChange point histogram data for a group of field/value pairs." + ], + "path": "x-pack/packages/ml/agg_utils/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.ChangePointGroupHistogram.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "x-pack/packages/ml/agg_utils/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.ChangePointGroupHistogram.histogram", + "type": "Array", + "tags": [], + "label": "histogram", + "description": [], + "signature": [ + { + "pluginId": "@kbn/ml-agg-utils", + "scope": "server", + "docId": "kibKbnMlAggUtilsPluginApi", + "section": "def-server.ChangePointHistogramItem", + "text": "ChangePointHistogramItem" + }, + "[]" + ], + "path": "x-pack/packages/ml/agg_utils/src/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index adadb5a8d1141..a0ecdade3f9e6 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact Machine Learning UI for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 58 | 2 | 39 | 3 | +| 64 | 2 | 44 | 3 | ## Server diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 1bd52ce9c5bc8..32da79b32c407 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: 2022-09-20 +date: 2022-09-22 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_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 372c7969c3806..7a92565583d64 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 902578461f024..e108034ba2c2d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index cbc5ae1b1ded5..98b48440df6fd 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: 2022-09-20 +date: 2022-09-22 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 42c37c78b5178..9d676c1c06397 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 44d6ca0550963..b4483ef89d466 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 4798bfbf8987a..0e8047259ac54 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: 2022-09-20 +date: 2022-09-22 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 b9be224e32a5b..725c12f183891 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 40fa48fd2b023..6f4a984b14a3a 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 1509876385b1f..5f00051849f1c 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 0e8c7c73e7c84..c1a782c141bc6 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 6e19f121fd9ad..d5e955d1f2595 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 2ffb661273b81..77bceec83858e 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: 2022-09-20 +date: 2022-09-22 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_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 7e05e5af14825..b7856995a8108 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: 2022-09-20 +date: 2022-09-22 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 d94c62d645fd5..bc4e23e2e983d 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: 2022-09-20 +date: 2022-09-22 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 63986072c7322..82e3fb0ae468c 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: 2022-09-20 +date: 2022-09-22 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 7a0b917859a62..a0d2052ae7794 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: 2022-09-20 +date: 2022-09-22 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 6177ab6c92c8d..8117732d57ad9 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: 2022-09-20 +date: 2022-09-22 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 4b880041473c4..c403a28913447 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 28cf4efef38a8..a3ba38ac91210 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: 2022-09-20 +date: 2022-09-22 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 e2232050612c1..e59220c83f958 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 44b7742d2b5c7..b77cb1ff96d10 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index b979309b06fc5..c1d3390dd76b6 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: 2022-09-20 +date: 2022-09-22 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 fd74c70883973..778caf257464d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 0acf0fc188b95..d326fdb690acd 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: 2022-09-20 +date: 2022-09-22 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 be7aff880057c..25471b3d51cd0 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: 2022-09-20 +date: 2022-09-22 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 bdba63997b786..dfbc5720c2a3a 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 5cf5c679becea..793150d936933 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: 2022-09-20 +date: 2022-09-22 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_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 8032ad63fef85..e1daf06b3b274 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 50578bc364b66..a4cf83db9324e 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 453fd4a07cd2c..8658ea40c1995 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: 2022-09-20 +date: 2022-09-22 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.devdocs.json b/api_docs/kbn_shared_ux_card_no_data.devdocs.json index a97e211368c5c..c1e08d1b9a31f 100644 --- a/api_docs/kbn_shared_ux_card_no_data.devdocs.json +++ b/api_docs/kbn_shared_ux_card_no_data.devdocs.json @@ -186,7 +186,11 @@ "signature": [ "{ children?: React.ReactNode; description?: React.ReactNode; category?: string | undefined; onError?: React.ReactEventHandler | undefined; hidden?: boolean | undefined; icon?: React.ReactElement<", "EuiIconProps", - ", string | React.JSXElementConstructor> | null | undefined; id?: string | undefined; image?: string | React.ReactElement> | undefined; className?: string | undefined; title?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; onChange?: React.FormEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; security?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; lang?: string | undefined; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; contentEditable?: \"inherit\" | Booleanish | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: React.CSSProperties | undefined; tabIndex?: number | undefined; translate?: \"no\" | \"yes\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; prefix?: string | undefined; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"none\" | \"email\" | \"search\" | \"text\" | \"tel\" | \"url\" | \"numeric\" | \"decimal\" | undefined; is?: string | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"date\" | \"location\" | \"time\" | \"page\" | \"false\" | \"true\" | \"step\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"none\" | \"copy\" | \"link\" | \"execute\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"grid\" | \"menu\" | \"false\" | \"true\" | \"dialog\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"false\" | \"true\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"all\" | \"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"other\" | \"ascending\" | \"descending\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; 'data-test-subj'?: string | undefined; href?: string | undefined; rel?: string | undefined; target?: string | undefined; paddingSize?: \"none\" | \"m\" | \"s\" | \"xs\" | \"l\" | \"xl\" | undefined; button?: React.ReactNode; footer?: React.ReactNode; hasBorder?: boolean | undefined; textAlign?: CardAlignment | undefined; titleElement?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"span\" | undefined; titleSize?: \"s\" | \"xs\" | undefined; betaBadgeProps?: Partial<(", + ", string | React.JSXElementConstructor> | null | undefined; id?: string | undefined; image?: string | React.ReactElement> | undefined; className?: string | undefined; title?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; onChange?: React.FormEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; security?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; lang?: string | undefined; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; contentEditable?: \"inherit\" | Booleanish | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: React.CSSProperties | undefined; tabIndex?: number | undefined; translate?: \"no\" | \"yes\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; prefix?: string | undefined; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"none\" | \"email\" | \"search\" | \"text\" | \"tel\" | \"url\" | \"numeric\" | \"decimal\" | undefined; is?: string | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"date\" | \"location\" | \"time\" | \"page\" | \"false\" | \"true\" | \"step\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"none\" | \"copy\" | \"link\" | \"execute\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"grid\" | \"menu\" | \"false\" | \"true\" | \"dialog\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"false\" | \"true\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"all\" | \"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"other\" | \"ascending\" | \"descending\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; 'data-test-subj'?: string | undefined; css?: ", + "Interpolation", + "<", + "Theme", + ">; href?: string | undefined; rel?: string | undefined; target?: string | undefined; paddingSize?: \"none\" | \"m\" | \"s\" | \"xs\" | \"l\" | \"xl\" | undefined; button?: React.ReactNode; footer?: React.ReactNode; hasBorder?: boolean | undefined; textAlign?: CardAlignment | undefined; titleElement?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"span\" | undefined; titleSize?: \"s\" | \"xs\" | undefined; betaBadgeProps?: Partial<(", "CommonProps", " & ", "DisambiguateSet", diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 8f8e2cb2d0840..0dc7d8de76359 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: 2022-09-20 +date: 2022-09-22 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 6b5cf577d2f23..35914b6d9b59d 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: 2022-09-20 +date: 2022-09-22 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_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index ad46fe9c6337f..a2b9f784ff86e 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: 2022-09-20 +date: 2022-09-22 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_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 650f6b6de6657..bb96516fa0fbd 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: 2022-09-20 +date: 2022-09-22 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 c526e3b3d7f6d..d804aea444daa 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: 2022-09-20 +date: 2022-09-22 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 620084197a3fc..31a07313d1af3 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: 2022-09-20 +date: 2022-09-22 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 53fe5c2b52348..4f57dbfd4399d 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: 2022-09-20 +date: 2022-09-22 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 1cc01f097e1f5..673c8bb7bb567 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: 2022-09-20 +date: 2022-09-22 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 294ae75484592..135bb6bd1caad 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: 2022-09-20 +date: 2022-09-22 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 e228084b30143..3fd6fad8f1a41 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: 2022-09-20 +date: 2022-09-22 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 02191cc87e73d..6334f26246c3b 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: 2022-09-20 +date: 2022-09-22 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 aa10464a8a61e..e737f1156e683 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: 2022-09-20 +date: 2022-09-22 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 0ac409eb1f626..196d53a7ba389 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: 2022-09-20 +date: 2022-09-22 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 113d794aeac30..b49adcd6ea044 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: 2022-09-20 +date: 2022-09-22 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 784d4bbb54fd0..a25944fdb2882 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: 2022-09-20 +date: 2022-09-22 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 5097f00e1394f..54f45ccf3da49 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: 2022-09-20 +date: 2022-09-22 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_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 576f834d1cf4e..7de045590c6f7 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: 2022-09-20 +date: 2022-09-22 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 75a3bdd71f467..f65e964f0848a 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: 2022-09-20 +date: 2022-09-22 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 9fc0023c3105f..81fb8c51ac022 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: 2022-09-20 +date: 2022-09-22 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 6e659b6749e9b..8cb95096540e5 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index ae651ff396bb2..23c2afad25bf7 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index a5bf07dccef17..aae68b0778949 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: 2022-09-20 +date: 2022-09-22 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_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index bec95f5dd0edf..a0578b5514cd2 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 15d38db3ce3f8..7e7f3747c59ee 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: 2022-09-20 +date: 2022-09-22 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 286713890035f..5a733985a17a5 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: 2022-09-20 +date: 2022-09-22 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 6dcb31e2935ba..f0012bc98f817 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: 2022-09-20 +date: 2022-09-22 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 f55b78a57dd13..b7f2429457b19 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 368a220dbf198..9dd5c24776896 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index a9bab53131ebc..5e84097dcef14 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: 2022-09-20 +date: 2022-09-22 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 32bfe1cb2f68b..b6729e8555502 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 4e928b81cc9e2..8d30e51ee9228 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index 6885c76d34578..94e15ec19c237 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 02d17639ecacb..1abf7a1158dc8 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index eee639c93e87c..0c95e31f10e68 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: 2022-09-20 +date: 2022-09-22 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_theme.devdocs.json b/api_docs/kbn_ui_theme.devdocs.json index c378173a45f13..6dcb3db66223b 100644 --- a/api_docs/kbn_ui_theme.devdocs.json +++ b/api_docs/kbn_ui_theme.devdocs.json @@ -54,7 +54,7 @@ "label": "Theme", "description": [], "signature": [ - "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiPopoverArrowSize: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" + "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" ], "path": "packages/kbn-ui-theme/src/theme.ts", "deprecated": false, @@ -86,7 +86,7 @@ "label": "euiDarkVars", "description": [], "signature": [ - "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiPopoverArrowSize: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" + "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" ], "path": "packages/kbn-ui-theme/src/theme.ts", "deprecated": false, @@ -101,7 +101,7 @@ "label": "euiLightVars", "description": [], "signature": [ - "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiPopoverArrowSize: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" + "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" ], "path": "packages/kbn-ui-theme/src/theme.ts", "deprecated": false, @@ -118,7 +118,7 @@ "\nEUI Theme vars that automatically adjust to light/dark theme" ], "signature": [ - "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiPopoverArrowSize: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" + "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" ], "path": "packages/kbn-ui-theme/src/theme.ts", "deprecated": false, diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 76f96eb38b6e5..a6871ad14be19 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 93b401d3f3979..d996627374c01 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: 2022-09-20 +date: 2022-09-22 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 70061bff2f395..2ceeeae778cfe 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: 2022-09-20 +date: 2022-09-22 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 0161668a526f1..4e1ba1953d065 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: 2022-09-20 +date: 2022-09-22 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 58b9be7f437c9..eb5beac7065ff 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 79ee7cd9f2a58..4667f30cbf1f4 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 3fe57352b3d88..34c0d11826ba1 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.devdocs.json b/api_docs/kibana_react.devdocs.json index 5677d3738d59b..67182ef5aede3 100644 --- a/api_docs/kibana_react.devdocs.json +++ b/api_docs/kibana_react.devdocs.json @@ -672,7 +672,7 @@ "label": "KibanaThemeProvider", "description": [], "signature": [ - "({ theme$, children }: React.PropsWithChildren) => JSX.Element" + "({ theme$, modify, children }: React.PropsWithChildren) => JSX.Element" ], "path": "src/plugins/kibana_react/public/theme/kibana_theme_provider.tsx", "deprecated": false, @@ -683,7 +683,7 @@ "id": "def-public.KibanaThemeProvider.$1", "type": "CompoundType", "tags": [], - "label": "{ theme$, children }", + "label": "{ theme$, modify, children }", "description": [], "signature": [ "React.PropsWithChildren" @@ -3893,7 +3893,7 @@ "label": "eui", "description": [], "signature": [ - "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiPopoverArrowSize: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" + "{ euiBadgeGroupGutterTypes: { gutterExtraSmall: string; gutterSmall: string; }; euiButtonEmptyTypes: { primary: string; danger: string; disabled: string; ghost: string; text: string; success: string; warning: string; }; euiCallOutTypes: { primary: string; success: string; warning: string; danger: string; }; euiCardSpacing: string; euiCardBottomNodeHeight: string; euiCardSelectButtonBorders: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardSelectButtonBackgrounds: { text: string; primary: string; success: string; danger: string; ghost: string; }; euiCardPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCheckableCardPadding: string; euiCodeBlockPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiCollapsibleNavGroupLightBackgroundColor: string; euiCollapsibleNavGroupDarkBackgroundColor: string; euiCollapsibleNavGroupDarkHighContrastColor: string; euiColorPickerValueRange0: string; euiColorPickerValueRange1: string; euiColorPickerSaturationRange0: string; euiColorPickerSaturationRange1: string; euiColorPickerIndicatorSize: string; euiColorPickerWidth: string; euiColorPaletteDisplaySizes: { sizeExtraSmall: string; sizeSmall: string; sizeMedium: string; }; euiContextMenuWidth: string; euiControlBarBackground: string; euiControlBarText: string; euiControlBarBorderColor: string; euiControlBarInitialHeight: string; euiControlBarMaxHeight: string; euiControlBarHeights: { s: string; m: string; l: string; }; euiDataGridPrefix: string; euiDataGridStyles: string; euiZDataGrid: number; euiZHeaderBelowDataGrid: number; euiZDataGridCellPopover: number; euiDataGridColumnResizerWidth: string; euiDataGridPopoverMaxHeight: string; euiDataGridCellPaddingS: string; euiDataGridCellPaddingM: string; euiDataGridCellPaddingL: string; euiDataGridVerticalBorder: string; euiSuperDatePickerWidth: string; euiSuperDatePickerButtonWidth: string; euiDragAndDropSpacing: { s: string; m: string; l: string; }; euiEmptyPromptContentMaxWidth: string; gutterTypes: { gutterExtraSmall: string; gutterSmall: string; gutterMedium: string; gutterLarge: string; gutterExtraLarge: string; }; fractions: { fourths: { percentage: string; count: number; }; thirds: { percentage: string; count: number; }; halves: { percentage: string; count: number; }; single: { percentage: string; count: number; }; }; flyoutSizes: { small: { min: string; width: string; max: string; }; medium: { min: string; width: string; max: string; }; large: { min: string; width: string; max: string; }; }; euiFlyoutBorder: string; euiFlyoutPaddingModifiers: { paddingNone: number; paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiFilePickerTallHeight: string; euiRangeLevelColors: { primary: string; success: string; warning: string; danger: string; }; textareaResizing: { vertical: string; horizontal: string; both: string; none: string; }; euiHeaderLinksGutterSizes: { gutterXS: string; gutterS: string; gutterM: string; gutterL: string; }; euiKeyPadMenuSize: string; euiKeyPadMenuMarginSize: string; euiListGroupItemHoverBackground: string; euiListGroupItemHoverBackgroundGhost: string; euiListGroupGutterTypes: { gutterSmall: string; gutterMedium: string; }; euiListGroupItemColorTypes: { primary: string; text: string; subdued: string; ghost: string; }; euiListGroupItemSizeTypes: { xSmall: string; small: string; medium: string; large: string; }; euiMarkdownEditorMinHeight: string; euiResizableButtonTransitionSpeed: string; euiResizableButtonSize: string; euiSelectableListItemBorder: string; euiSelectableListItemPadding: string; euiSelectableTemplateSitewideTypes: { application: { color: string; 'font-weight': number; }; deployment: { color: string; 'font-weight': number; }; article: { color: string; 'font-weight': number; }; case: { color: string; 'font-weight': number; }; platform: { color: string; 'font-weight': number; }; }; euiSideNavEmphasizedBackgroundColor: string; euiSideNavRootTextcolor: string; euiSideNavBranchTextcolor: string; euiSideNavSelectedTextcolor: string; euiSideNavDisabledTextcolor: string; euiStepNumberSize: string; euiStepNumberSmallSize: string; euiStepNumberMargin: string; euiStepStatusColorsToFade: { warning: string; danger: string; disabled: string; incomplete: string; }; euiSuggestItemColors: { tint0: string; tint1: string; tint2: string; tint3: string; tint4: string; tint5: string; tint6: string; tint7: string; tint8: string; tint9: string; tint10: string; }; euiTableCellContentPadding: string; euiTableCellContentPaddingCompressed: string; euiTableCellCheckboxWidth: string; euiTableActionsAreaWidth: string; euiTableHoverColor: string; euiTableSelectedColor: string; euiTableHoverSelectedColor: string; euiTableActionsBorderColor: string; euiTableHoverClickableColor: string; euiTableFocusClickableColor: string; euiContrastRatioText: number; euiContrastRatioGraphic: number; euiContrastRatioDisabled: number; euiAnimSlightBounce: string; euiAnimSlightResistance: string; euiAnimSpeedExtraFast: string; euiAnimSpeedFast: string; euiAnimSpeedNormal: string; euiAnimSpeedSlow: string; euiAnimSpeedExtraSlow: string; euiBorderWidthThin: string; euiBorderWidthThick: string; euiBorderColor: string; euiBorderRadius: string; euiBorderRadiusSmall: string; euiBorderThick: string; euiBorderThin: string; euiBorderEditable: string; euiButtonHeight: string; euiButtonHeightSmall: string; euiButtonHeightXSmall: string; euiButtonColorDisabled: string; euiButtonColorDisabledText: string; euiButtonColorGhostDisabled: string; euiButtonTypes: { primary: string; accent: string; success: string; warning: string; danger: string; ghost: string; text: string; }; euiCodeBlockBackgroundColor: string; euiCodeBlockColor: string; euiCodeBlockSelectedBackgroundColor: string; euiCodeBlockCommentColor: string; euiCodeBlockSelectorTagColor: string; euiCodeBlockStringColor: string; euiCodeBlockTagColor: string; euiCodeBlockNameColor: string; euiCodeBlockNumberColor: string; euiCodeBlockKeywordColor: string; euiCodeBlockFunctionTitleColor: string; euiCodeBlockTypeColor: string; euiCodeBlockAttributeColor: string; euiCodeBlockSymbolColor: string; euiCodeBlockParamsColor: string; euiCodeBlockMetaColor: string; euiCodeBlockTitleColor: string; euiCodeBlockSectionColor: string; euiCodeBlockAdditionColor: string; euiCodeBlockDeletionColor: string; euiCodeBlockSelectorClassColor: string; euiCodeBlockSelectorIdColor: string; euiPaletteColorBlind: { euiColorVis0: { graphic: string; behindText: string; }; euiColorVis1: { graphic: string; behindText: string; }; euiColorVis2: { graphic: string; behindText: string; }; euiColorVis3: { graphic: string; behindText: string; }; euiColorVis4: { graphic: string; behindText: string; }; euiColorVis5: { graphic: string; behindText: string; }; euiColorVis6: { graphic: string; behindText: string; }; euiColorVis7: { graphic: string; behindText: string; }; euiColorVis8: { graphic: string; behindText: string; }; euiColorVis9: { graphic: string; behindText: string; }; }; euiPaletteColorBlindKeys: string; euiColorVis0: string; euiColorVis1: string; euiColorVis2: string; euiColorVis3: string; euiColorVis4: string; euiColorVis5: string; euiColorVis6: string; euiColorVis7: string; euiColorVis8: string; euiColorVis9: string; euiColorVis0_behindText: string; euiColorVis1_behindText: string; euiColorVis2_behindText: string; euiColorVis3_behindText: string; euiColorVis4_behindText: string; euiColorVis5_behindText: string; euiColorVis6_behindText: string; euiColorVis7_behindText: string; euiColorVis8_behindText: string; euiColorVis9_behindText: string; euiFontWeightLight: number; euiFontWeightRegular: number; euiFontWeightMedium: number; euiFontWeightSemiBold: number; euiFontWeightBold: number; euiCodeFontWeightRegular: number; euiCodeFontWeightBold: number; euiFormMaxWidth: string; euiFormControlHeight: string; euiFormControlCompressedHeight: string; euiFormControlPadding: string; euiFormControlCompressedPadding: string; euiFormControlBorderRadius: string; euiFormControlCompressedBorderRadius: string; euiRadioSize: string; euiCheckBoxSize: string; euiCheckboxBorderRadius: string; euiSwitchHeight: string; euiSwitchWidth: string; euiSwitchThumbSize: string; euiSwitchIconHeight: string; euiSwitchHeightCompressed: string; euiSwitchWidthCompressed: string; euiSwitchThumbSizeCompressed: string; euiSwitchHeightMini: string; euiSwitchWidthMini: string; euiSwitchThumbSizeMini: string; euiFormBackgroundColor: string; euiFormBackgroundDisabledColor: string; euiFormBackgroundReadOnlyColor: string; euiFormBorderOpaqueColor: string; euiFormBorderColor: string; euiFormBorderDisabledColor: string; euiFormCustomControlDisabledIconColor: string; euiFormCustomControlBorderColor: string; euiFormControlDisabledColor: string; euiFormControlBoxShadow: string; euiFormControlPlaceholderText: string; euiFormInputGroupLabelBackground: string; euiFormInputGroupBorder: string; euiSwitchOffColor: string; euiFormControlIconSizes: { small: string; medium: string; large: string; xLarge: string; xxLarge: string; }; euiFormControlLayoutGroupInputHeight: string; euiFormControlLayoutGroupInputCompressedHeight: string; euiFormControlLayoutGroupInputCompressedBorderRadius: string; euiRangeTrackColor: string; euiRangeThumbRadius: string; euiRangeThumbHeight: string; euiRangeThumbWidth: string; euiRangeThumbBorderColor: string; euiRangeTrackWidth: string; euiRangeTrackHeight: string; euiRangeTrackBorderWidth: number; euiRangeTrackBorderColor: string; euiRangeTrackRadius: string; euiRangeDisabledOpacity: number; euiRangeHighlightHeight: string; euiHeaderBackgroundColor: string; euiHeaderDarkBackgroundColor: string; euiHeaderBorderColor: string; euiHeaderBreadcrumbColor: string; euiHeaderHeight: string; euiHeaderChildSize: string; euiHeaderHeightCompensation: string; euiPageDefaultMaxWidth: string; euiPageSidebarMinWidth: string; euiPanelPaddingModifiers: { paddingSmall: string; paddingMedium: string; paddingLarge: string; }; euiPanelBorderRadiusModifiers: { borderRadiusNone: number; borderRadiusMedium: string; }; euiPanelBackgroundColorModifiers: { transparent: string; plain: string; subdued: string; accent: string; primary: string; success: string; warning: string; danger: string; }; euiBreakpoints: { xs: number; s: string; m: string; l: string; xl: string; }; euiBreakpointKeys: string; euiShadowColor: string; euiSize: string; euiSizeXS: string; euiSizeS: string; euiSizeM: string; euiSizeL: string; euiSizeXL: string; euiSizeXXL: string; euiButtonMinWidth: string; euiScrollBar: string; euiScrollBarCorner: string; euiScrollBarCornerThin: string; euiFocusRingColor: string; euiFocusRingAnimStartColor: string; euiFocusRingAnimStartSize: string; euiFocusRingAnimStartSizeLarge: string; euiFocusRingSizeLarge: string; euiFocusRingSize: string; euiFocusTransparency: number; euiFocusTransparencyPercent: string; euiFocusBackgroundColor: string; euiTooltipBackgroundColor: string; euiTooltipBorderColor: string; euiTooltipAnimations: { top: string; left: string; bottom: string; right: string; }; euiFontFamily: string; euiCodeFontFamily: string; euiFontFeatureSettings: string; euiTextScale: string; euiFontSize: string; euiFontSizeXS: string; euiFontSizeS: string; euiFontSizeM: string; euiFontSizeL: string; euiFontSizeXL: string; euiFontSizeXXL: string; euiLineHeight: number; euiBodyLineHeight: number; euiTitles: { xxxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xxs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; xs: { 'font-size': string; 'line-height': string; 'font-weight': number; }; s: { 'font-size': string; 'line-height': string; 'font-weight': number; }; m: { 'font-size': string; 'line-height': string; 'font-weight': number; }; l: { 'font-size': string; 'line-height': string; 'font-weight': number; }; }; euiZLevel0: number; euiZLevel1: number; euiZLevel2: number; euiZLevel3: number; euiZLevel4: number; euiZLevel5: number; euiZLevel6: number; euiZLevel7: number; euiZLevel8: number; euiZLevel9: number; euiZToastList: number; euiZModal: number; euiZMask: number; euiZNavigation: number; euiZContentMenu: number; euiZHeader: number; euiZFlyout: number; euiZMaskBelowHeader: number; euiZContent: number; euiColorGhost: string; euiColorInk: string; euiColorPrimary: string; euiColorAccent: string; euiColorSuccess: string; euiColorWarning: string; euiColorDanger: string; euiColorEmptyShade: string; euiColorLightestShade: string; euiColorLightShade: string; euiColorMediumShade: string; euiColorDarkShade: string; euiColorDarkestShade: string; euiColorFullShade: string; euiPageBackgroundColor: string; euiColorHighlight: string; euiTextColor: string; euiTitleColor: string; euiTextSubduedColor: string; euiColorDisabled: string; euiColorPrimaryText: string; euiColorSuccessText: string; euiColorAccentText: string; euiColorWarningText: string; euiColorDangerText: string; euiColorDisabledText: string; euiLinkColor: string; euiColorChartLines: string; euiColorChartBand: string; euiDatePickerCalendarWidth: string; euiDatePickerPadding: string; euiDatePickerGap: string; euiDatePickerCalendarColumns: number; euiDatePickerButtonSize: string; euiDatePickerMinControlWidth: string; euiDatePickerMaxControlWidth: string; euiButtonDefaultTransparency: number; euiButtonFontWeight: number; euiRangeHighlightColor: string; euiRangeThumbBackgroundColor: string; euiRangeTrackCompressedHeight: string; euiRangeHighlightCompressedHeight: string; euiRangeHeight: string; euiRangeCompressedHeight: string; euiStepStatusColors: { default: string; complete: string; warning: string; danger: string; }; }" ], "path": "src/plugins/kibana_react/common/eui_styled_components.tsx", "deprecated": false, diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 5dc1c70a12e19..97f3a78ded925 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: 2022-09-20 +date: 2022-09-22 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 cbde41998f2e9..deafbf79b1aed 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: 2022-09-20 +date: 2022-09-22 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 1b919dd1a45b7..7665687eeeb21 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 724ee8dd1b668..4fc56c34842e9 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -4504,7 +4504,17 @@ "signature": [ "((state: P, references?: ", "SavedObjectReference", - "[] | undefined) => T) | undefined" + "[] | undefined, initialContext?: ", + { + "pluginId": "uiActions", + "scope": "public", + "docId": "kibUiActionsPluginApi", + "section": "def-public.VisualizeFieldContext", + "text": "VisualizeFieldContext" + }, + " | ", + "VisualizeEditorContext", + " | undefined) => T) | undefined" ], "path": "x-pack/plugins/lens/public/types.ts", "deprecated": false, @@ -4540,6 +4550,30 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.fromPersistableState.$3", + "type": "CompoundType", + "tags": [], + "label": "initialContext", + "description": [], + "signature": [ + { + "pluginId": "uiActions", + "scope": "public", + "docId": "kibUiActionsPluginApi", + "section": "def-public.VisualizeFieldContext", + "text": "VisualizeFieldContext" + }, + " | ", + "VisualizeEditorContext", + " | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] @@ -4642,6 +4676,85 @@ ], "returnComment": [] }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.cloneLayer", + "type": "Function", + "tags": [], + "label": "cloneLayer", + "description": [ + "Reset button on each layer triggers this" + ], + "signature": [ + "((state: T, layerId: string, newLayerId: string, clonedIDsMap: Map) => T) | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "lens", + "id": "def-public.Visualization.cloneLayer.$1", + "type": "Uncategorized", + "tags": [], + "label": "state", + "description": [], + "signature": [ + "T" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.cloneLayer.$2", + "type": "string", + "tags": [], + "label": "layerId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.cloneLayer.$3", + "type": "string", + "tags": [], + "label": "newLayerId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.cloneLayer.$4", + "type": "Object", + "tags": [], + "label": "clonedIDsMap", + "description": [], + "signature": [ + "Map" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "lens", "id": "def-public.Visualization.removeLayer", diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 653131ded02e2..76385b837391c 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 640 | 0 | 553 | 41 | +| 646 | 0 | 558 | 42 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 64f0bdfad6df0..c56a3ae4b8e16 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: 2022-09-20 +date: 2022-09-22 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 a31914ec5badf..e68b7f3ca1944 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.devdocs.json b/api_docs/licensing.devdocs.json index 0b456fa70029f..335c7891795d3 100644 --- a/api_docs/licensing.devdocs.json +++ b/api_docs/licensing.devdocs.json @@ -2142,6 +2142,10 @@ "plugin": "searchprofiler", "path": "x-pack/plugins/searchprofiler/server/plugin.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts" + }, { "plugin": "snapshotRestore", "path": "x-pack/plugins/snapshot_restore/server/services/license.ts" diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index c9ddbed424930..ea9fad5915ad3 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index ad79875fc4108..c63719ad85397 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index cb04898032b5f..b5bc104a0ce60 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index fb9923f8b2d6d..5fcfd0203431c 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 3b3b444c80646..9cc6b1a542f2f 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 9eacb8772d83d..0a43ee6b4356d 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index ec3b931092bb9..7e9bbc61325bd 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: 2022-09-20 +date: 2022-09-22 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 ec07fb98e351d..a2551bb06b839 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: 2022-09-20 +date: 2022-09-22 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 e4db7f2310e13..d832b2f6ba19a 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: 2022-09-20 +date: 2022-09-22 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 9b47c9ea3d5d1..448efa7620ec5 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 25c742a70b12e..4fae8b05f4e6a 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -738,11 +738,7 @@ }, " | undefined; list: () => string[]; }; selectedAlertId?: string | undefined; } & ", "CommonProps", - " & { as?: \"div\" | undefined; } & _EuiFlyoutProps & Omit, HTMLDivElement>, keyof _EuiFlyoutProps> & Omit, HTMLDivElement>, \"key\" | keyof React.HTMLAttributes | \"css\"> & { ref?: React.RefObject | ((instance: HTMLDivElement | null) => void) | null | undefined; }, \"children\" | \"onError\" | \"hidden\" | \"color\" | \"id\" | \"title\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"as\" | keyof ", - "CommonProps", - " | keyof React.ClassAttributes | keyof _EuiFlyoutProps>, \"children\" | \"onError\" | \"hidden\" | \"color\" | \"id\" | \"alert\" | \"title\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"key\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"as\" | \"css\" | keyof ", - "CommonProps", - " | \"alerts\" | keyof _EuiFlyoutProps | \"isInApp\" | \"observabilityRuleTypeRegistry\" | \"selectedAlertId\"> & { ref?: React.RefObject | ((instance: HTMLDivElement | null) => void) | null | undefined; }> & { readonly _result: ({ alert, alerts, isInApp, observabilityRuleTypeRegistry, onClose, selectedAlertId, }: AlertsFlyoutProps) => JSX.Element | null; }" + " & { as?: \"div\" | undefined; } & _EuiFlyoutProps & Omit, HTMLDivElement>, keyof _EuiFlyoutProps> & Omit, HTMLDivElement>, \"key\" | keyof React.HTMLAttributes | \"css\"> & { ref?: React.RefObject | ((instance: HTMLDivElement | null) => void) | null | undefined; }, \"children\" | \"ref\" | \"type\" | \"onError\" | \"hidden\" | \"color\" | \"id\" | \"className\" | \"size\" | \"title\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"key\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"onClose\" | \"data-test-subj\" | \"css\" | \"as\" | \"paddingSize\" | \"focusTrapProps\" | \"ownFocus\" | \"maxWidth\" | \"hideCloseButton\" | \"closeButtonAriaLabel\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\">, \"children\" | \"type\" | \"onError\" | \"hidden\" | \"color\" | \"id\" | \"alert\" | \"className\" | \"size\" | \"title\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"key\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"onClose\" | \"data-test-subj\" | \"css\" | \"as\" | \"paddingSize\" | \"focusTrapProps\" | \"ownFocus\" | \"alerts\" | \"maxWidth\" | \"hideCloseButton\" | \"closeButtonAriaLabel\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"isInApp\" | \"observabilityRuleTypeRegistry\" | \"selectedAlertId\"> & { ref?: React.RefObject | ((instance: HTMLDivElement | null) => void) | null | undefined; }> & { readonly _result: ({ alert, alerts, isInApp, observabilityRuleTypeRegistry, onClose, selectedAlertId, }: AlertsFlyoutProps) => JSX.Element | null; }" ], "path": "x-pack/plugins/observability/public/index.ts", "deprecated": false, @@ -7717,13 +7713,35 @@ "label": "ObservabilityAPIReturnType", "description": [], "signature": [ - "{ \"POST /api/observability/slos\"?: ", + "{ \"DELETE /api/observability/slos/{id}\"?: ", + "ServerRoute", + "<\"DELETE /api/observability/slos/{id}\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", void, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "> | undefined; \"POST /api/observability/slos\"?: ", "ServerRoute", "<\"POST /api/observability/slos\", ", "TypeC", "<{ body: ", - "IntersectionC", - "<[", "TypeC", "<{ name: ", "StringC", @@ -7821,13 +7839,7 @@ "TypeC", "<{ target: ", "NumberC", - "; }>; }>, ", - "PartialC", - "<{ settings: ", - "PartialC", - "<{ destination_index: ", - "StringC", - "; }>; }>]>; }>, ", + "; }>; }>; }>, ", { "pluginId": "observability", "scope": "server", @@ -7905,13 +7917,35 @@ "label": "ObservabilityServerRouteRepository", "description": [], "signature": [ - "{ \"POST /api/observability/slos\"?: ", + "{ \"DELETE /api/observability/slos/{id}\"?: ", + "ServerRoute", + "<\"DELETE /api/observability/slos/{id}\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", void, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "> | undefined; \"POST /api/observability/slos\"?: ", "ServerRoute", "<\"POST /api/observability/slos\", ", "TypeC", "<{ body: ", - "IntersectionC", - "<[", "TypeC", "<{ name: ", "StringC", @@ -8009,13 +8043,7 @@ "TypeC", "<{ target: ", "NumberC", - "; }>; }>, ", - "PartialC", - "<{ settings: ", - "PartialC", - "<{ destination_index: ", - "StringC", - "; }>; }>]>; }>, ", + "; }>; }>; }>, ", { "pluginId": "observability", "scope": "server", @@ -8832,6 +8860,112 @@ } ] }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics", + "type": "Object", + "tags": [], + "label": "[enableServiceMetrics]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceMetrics.showInLabs", + "type": "boolean", + "tags": [], + "label": "showInLabs", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, { "parentPluginId": "observability", "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting", @@ -9450,6 +9584,126 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics", + "type": "Object", + "tags": [], + "label": "[enableAwsLambdaMetrics]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"boolean\"" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableAwsLambdaMetrics.showInLabs", + "type": "boolean", + "tags": [], + "label": "showInLabs", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "initialIsOpen": false @@ -9879,6 +10133,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-common.enableAwsLambdaMetrics", + "type": "string", + "tags": [], + "label": "enableAwsLambdaMetrics", + "description": [], + "signature": [ + "\"observability:enableAwsLambdaMetrics\"" + ], + "path": "x-pack/plugins/observability/common/ui_settings_keys.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-common.enableComparisonByDefault", @@ -9954,6 +10223,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-common.enableServiceMetrics", + "type": "string", + "tags": [], + "label": "enableServiceMetrics", + "description": [], + "signature": [ + "\"observability:apmEnableServiceMetrics\"" + ], + "path": "x-pack/plugins/observability/common/ui_settings_keys.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-common.maxSuggestions", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index f808ef503a4c0..636feb22aad94 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Observability UI](https://github.com/orgs/elastic/teams/observability-u | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 509 | 2 | 505 | 30 | +| 528 | 2 | 524 | 30 | ## Client diff --git a/api_docs/osquery.devdocs.json b/api_docs/osquery.devdocs.json index 8d45fc77730ef..bcaaa466f6ca5 100644 --- a/api_docs/osquery.devdocs.json +++ b/api_docs/osquery.devdocs.json @@ -48,6 +48,39 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "osquery", + "id": "def-public.OsqueryPluginStart.OsqueryResults", + "type": "Function", + "tags": [], + "label": "OsqueryResults", + "description": [], + "signature": [ + "(props: ", + "OsqueryActionResultsProps", + ") => JSX.Element" + ], + "path": "x-pack/plugins/osquery/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "osquery", + "id": "def-public.OsqueryPluginStart.OsqueryResults.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "OsqueryActionResultsProps" + ], + "path": "x-pack/plugins/osquery/public/shared_components/lazy_osquery_results.tsx", + "deprecated": false, + "trackAdoption": false + } + ] + }, { "parentPluginId": "osquery", "id": "def-public.OsqueryPluginStart.LiveQueryField", @@ -106,6 +139,53 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "osquery", + "id": "def-public.OsqueryPluginStart.fetchInstallationStatus", + "type": "Function", + "tags": [], + "label": "fetchInstallationStatus", + "description": [], + "signature": [ + "() => { loading: boolean; disabled: boolean; permissionDenied: boolean; }" + ], + "path": "x-pack/plugins/osquery/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "osquery", + "id": "def-public.OsqueryPluginStart.OsqueryResponseActionTypeForm", + "type": "Function", + "tags": [], + "label": "OsqueryResponseActionTypeForm", + "description": [], + "signature": [ + "(props: LazyOsqueryActionParamsFormProps) => JSX.Element" + ], + "path": "x-pack/plugins/osquery/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "osquery", + "id": "def-public.OsqueryPluginStart.OsqueryResponseActionTypeForm.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "LazyOsqueryActionParamsFormProps" + ], + "path": "x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx", + "deprecated": false, + "trackAdoption": false + } + ] } ], "lifecycle": "start", @@ -129,7 +209,40 @@ "path": "x-pack/plugins/osquery/server/types.ts", "deprecated": false, "trackAdoption": false, - "children": [], + "children": [ + { + "parentPluginId": "osquery", + "id": "def-server.OsqueryPluginSetup.osqueryCreateAction", + "type": "Function", + "tags": [], + "label": "osqueryCreateAction", + "description": [], + "signature": [ + "(payload: { agent_ids?: string[] | undefined; agent_all?: boolean | undefined; agent_platforms?: string[] | undefined; agent_policy_ids?: string[] | undefined; query?: string | undefined; queries?: { id: string; query: string; ecs_mapping: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; }; version: string; platform: string; }[] | { [x: string]: { query: string; id: string; ecs_mapping: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; version: string | undefined; platform: string | undefined; saved_query_id: string | undefined; }; } | undefined; saved_query_id?: string | undefined; ecs_mapping?: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; pack_id?: string | undefined; alert_ids?: string[] | undefined; case_ids?: string[] | undefined; event_ids?: string[] | undefined; metadata?: object | undefined; }) => void" + ], + "path": "x-pack/plugins/osquery/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "osquery", + "id": "def-server.OsqueryPluginSetup.osqueryCreateAction.$1", + "type": "Object", + "tags": [], + "label": "payload", + "description": [], + "signature": [ + "{ agent_ids?: string[] | undefined; agent_all?: boolean | undefined; agent_platforms?: string[] | undefined; agent_policy_ids?: string[] | undefined; query?: string | undefined; queries?: { id: string; query: string; ecs_mapping: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; }; version: string; platform: string; }[] | { [x: string]: { query: string; id: string; ecs_mapping: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; version: string | undefined; platform: string | undefined; saved_query_id: string | undefined; }; } | undefined; saved_query_id?: string | undefined; ecs_mapping?: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; pack_id?: string | undefined; alert_ids?: string[] | undefined; case_ids?: string[] | undefined; event_ids?: string[] | undefined; metadata?: object | undefined; }" + ], + "path": "x-pack/plugins/osquery/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], "lifecycle": "setup", "initialIsOpen": true }, diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 39fb0bbcd0e5d..55d8f562dfe26 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Security asset management](https://github.com/orgs/elastic/teams/securi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 14 | 0 | 14 | 2 | +| 21 | 0 | 21 | 3 | ## Client diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index f579ba9e630fb..baf9b97b47850 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: 2022-09-20 +date: 2022-09-22 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 | |--------------|----------|------------------------| -| 460 | 386 | 38 | +| 462 | 388 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 31149 | 179 | 20873 | 982 | +| 31370 | 180 | 21022 | 981 | ## Plugin Directory @@ -31,17 +31,18 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 1 | 32 | 2 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 9 | 0 | 0 | 2 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 370 | 0 | 361 | 22 | -| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 52 | +| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 51 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 80 | 1 | 71 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | | | [ResponseOps](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 87 | 0 | 71 | 28 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 264 | 2 | 249 | 9 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 29 | 0 | 24 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/@elastic/kibana-core) | Provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments. | 16 | 0 | 0 | 0 | | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 18 | 0 | 2 | 3 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 13 | 0 | 13 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 212 | 0 | 204 | 7 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2657 | 0 | 45 | 2 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2684 | 0 | 45 | 0 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 103 | 0 | 84 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 144 | 0 | 139 | 10 | @@ -53,7 +54,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | 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. | 966 | 0 | 208 | 1 | | | [Machine Learning 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. | 28 | 3 | 24 | 1 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 10 | 0 | 8 | 2 | -| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 96 | 0 | 79 | 4 | +| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 97 | 0 | 80 | 4 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 515 | 0 | 415 | 4 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | @@ -79,8 +80,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 222 | 0 | 95 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 5 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | -| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 6 | 2 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 974 | 3 | 874 | 17 | +| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 263 | 0 | 15 | 2 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 986 | 3 | 886 | 17 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -100,7 +101,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 615 | 3 | 418 | 9 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 640 | 0 | 553 | 41 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 646 | 0 | 558 | 42 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 8 | 0 | 8 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 3 | 0 | 3 | 0 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | @@ -114,15 +115,15 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 34 | 0 | 34 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | -| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 509 | 2 | 505 | 30 | -| | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 14 | 0 | 14 | 2 | +| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 528 | 2 | 524 | 30 | +| | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 21 | 0 | 21 | 3 | | painlessLab | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 243 | 2 | 187 | 12 | | | [profiling](https://github.com/orgs/elastic/teams/profiling-ui) | - | 14 | 1 | 14 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 4 | 0 | 4 | 0 | | | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Reporting Services enables applications to feature reports that the user can automate with Watcher and download later. | 36 | 0 | 16 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 21 | 0 | 21 | 0 | -| | [RAC](https://github.com/orgs/elastic/teams/rac) | - | 215 | 0 | 187 | 11 | +| | [RAC](https://github.com/orgs/elastic/teams/rac) | - | 230 | 0 | 202 | 10 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 24 | 0 | 19 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 193 | 2 | 152 | 5 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 16 | 0 | 16 | 0 | @@ -134,7 +135,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 27 | 0 | 8 | 4 | | searchprofiler | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 247 | 0 | 90 | 0 | -| | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 54 | 0 | 53 | 22 | +| | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 55 | 0 | 54 | 23 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds URL Service and sharing capabilities to Kibana | 114 | 0 | 55 | 10 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 22 | 1 | 22 | 1 | @@ -151,11 +152,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 450 | 1 | 344 | 33 | | | [Machine Learning 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 | [Kibana Localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 428 | 0 | 407 | 46 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 437 | 1 | 416 | 45 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 132 | 0 | 91 | 11 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 205 | 0 | 142 | 9 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 206 | 0 | 142 | 9 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 61 | 0 | 59 | 2 | -| | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 122 | 2 | 96 | 16 | +| | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 125 | 2 | 99 | 17 | | upgradeAssistant | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | urlDrilldown | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 12 | 0 | 12 | 0 | @@ -174,7 +175,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 53 | 0 | 50 | 5 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 611 | 12 | 582 | 14 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 631 | 12 | 602 | 14 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory @@ -295,9 +296,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 5 | 0 | 0 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | | | Kibana Core | - | 2 | 0 | 2 | 0 | -| | Kibana Core | - | 94 | 1 | 66 | 0 | -| | Kibana Core | - | 289 | 1 | 126 | 0 | -| | Kibana Core | - | 68 | 0 | 49 | 0 | +| | Kibana Core | - | 106 | 1 | 75 | 0 | +| | Kibana Core | - | 308 | 1 | 137 | 0 | +| | Kibana Core | - | 71 | 0 | 51 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | | | Kibana Core | - | 37 | 0 | 31 | 1 | | | Kibana Core | - | 4 | 0 | 4 | 0 | @@ -310,7 +311,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 100 | 0 | 74 | 43 | | | Kibana Core | - | 12 | 0 | 12 | 0 | | | Kibana Core | - | 225 | 0 | 82 | 0 | -| | Kibana Core | - | 66 | 0 | 66 | 4 | +| | Kibana Core | - | 69 | 0 | 69 | 4 | | | Kibana Core | - | 14 | 0 | 13 | 0 | | | Kibana Core | - | 99 | 1 | 86 | 0 | | | Kibana Core | - | 12 | 0 | 2 | 0 | @@ -330,7 +331,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 27 | 1 | 13 | 0 | | | Kibana Core | - | 28 | 1 | 27 | 1 | | | Kibana Core | - | 6 | 0 | 6 | 0 | -| | Kibana Core | - | 146 | 0 | 135 | 0 | +| | Kibana Core | - | 153 | 0 | 142 | 0 | | | Kibana Core | - | 8 | 0 | 8 | 2 | | | Kibana Core | - | 7 | 0 | 7 | 0 | | | [Owner missing] | - | 13 | 0 | 7 | 0 | @@ -346,6 +347,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 27 | 0 | 14 | 1 | | | Kibana Core | - | 7 | 0 | 3 | 0 | | | [Owner missing] | - | 222 | 1 | 168 | 12 | +| | Kibana Core | - | 11 | 0 | 11 | 0 | | | [Owner missing] | - | 2 | 0 | 1 | 0 | | | [Owner missing] | - | 20 | 0 | 16 | 0 | | | [Owner missing] | - | 2 | 0 | 0 | 0 | @@ -365,7 +367,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 8 | 0 | 8 | 0 | | | [Owner missing] | - | 6 | 0 | 1 | 1 | | | [Owner missing] | - | 534 | 1 | 1 | 0 | -| | Machine Learning UI | This package includes utility functions related to creating elasticsearch aggregation queries, data manipulation and verification. | 58 | 2 | 39 | 3 | +| | Machine Learning UI | This package includes utility functions related to creating elasticsearch aggregation queries, data manipulation and verification. | 64 | 2 | 44 | 3 | | | Machine Learning UI | A type guard to check record like object structures. | 3 | 0 | 2 | 0 | | | Machine Learning UI | Creates a deterministic number based hash out of a string. | 2 | 0 | 1 | 0 | | | [Owner missing] | - | 55 | 0 | 55 | 2 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index b7d546ea5bb4c..b870cf5f14637 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: 2022-09-20 +date: 2022-09-22 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 018931d925b07..8fb8adca44d0f 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index b74c2aad10f9a..ed97b5d7991c2 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: 2022-09-20 +date: 2022-09-22 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 ad3ea82997456..932bb602229a1 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: 2022-09-20 +date: 2022-09-22 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 b816147f94c89..7df9a27986349 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index 8d63050c8a9b0..424057fd0db70 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -411,7 +411,13 @@ "description": [], "signature": [ "({ indices, metaFields, allowNoIndex, }: { indices: string[]; metaFields: string[]; allowNoIndex: boolean; }) => Promise<", - "BrowserFields", + { + "pluginId": "ruleRegistry", + "scope": "common", + "docId": "kibRuleRegistryPluginApi", + "section": "def-common.BrowserFields", + "text": "BrowserFields" + }, ">" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", @@ -3985,6 +3991,200 @@ } ], "interfaces": [ + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField", + "type": "Interface", + "tags": [], + "label": "BrowserField", + "description": [], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.aggregatable", + "type": "boolean", + "tags": [], + "label": "aggregatable", + "description": [], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.category", + "type": "string", + "tags": [], + "label": "category", + "description": [], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.description", + "type": "CompoundType", + "tags": [], + "label": "description", + "description": [], + "signature": [ + "string | null | undefined" + ], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.example", + "type": "CompoundType", + "tags": [], + "label": "example", + "description": [], + "signature": [ + "string | number | null | undefined" + ], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.fields", + "type": "Object", + "tags": [], + "label": "fields", + "description": [], + "signature": [ + "{ readonly [x: string]: Partial<", + { + "pluginId": "ruleRegistry", + "scope": "common", + "docId": "kibRuleRegistryPluginApi", + "section": "def-common.BrowserField", + "text": "BrowserField" + }, + ">; }" + ], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.format", + "type": "string", + "tags": [], + "label": "format", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.indexes", + "type": "Array", + "tags": [], + "label": "indexes", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.searchable", + "type": "boolean", + "tags": [], + "label": "searchable", + "description": [], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.subType", + "type": "CompoundType", + "tags": [], + "label": "subType", + "description": [], + "signature": [ + "IFieldSubType", + " | undefined" + ], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.readFromDocValues", + "type": "boolean", + "tags": [], + "label": "readFromDocValues", + "description": [], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserField.runtimeField", + "type": "Object", + "tags": [], + "label": "runtimeField", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.RuntimeField", + "text": "RuntimeField" + }, + " | undefined" + ], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "ruleRegistry", "id": "def-common.RuleRegistrySearchRequestPagination", @@ -4039,6 +4239,29 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "ruleRegistry", + "id": "def-common.BrowserFields", + "type": "Type", + "tags": [], + "label": "BrowserFields", + "description": [], + "signature": [ + "{ [x: string]: Partial<", + { + "pluginId": "ruleRegistry", + "scope": "common", + "docId": "kibRuleRegistryPluginApi", + "section": "def-common.BrowserField", + "text": "BrowserField" + }, + ">; }" + ], + "path": "x-pack/plugins/rule_registry/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "ruleRegistry", "id": "def-common.ParsedTechnicalFields", diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 10e3e605e3dbe..8a0dbdd763edc 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; @@ -21,7 +21,7 @@ Contact [RAC](https://github.com/orgs/elastic/teams/rac) for questions regarding | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 215 | 0 | 187 | 11 | +| 230 | 0 | 202 | 10 | ## Server diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index aed78d453b32a..21f55bfde44f8 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: 2022-09-20 +date: 2022-09-22 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 f03ea7d006a30..0d3a66da043f4 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: 2022-09-20 +date: 2022-09-22 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 b2785bd5fedfc..35098620fec44 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.devdocs.json b/api_docs/saved_objects_management.devdocs.json index 3c4ff5fc143a3..1f64f3bea1abe 100644 --- a/api_docs/saved_objects_management.devdocs.json +++ b/api_docs/saved_objects_management.devdocs.json @@ -310,7 +310,11 @@ "section": "def-public.SavedObjectsManagementRecord", "text": "SavedObjectsManagementRecord" }, - "; headers?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; lang?: string | undefined; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; contentEditable?: \"inherit\" | Booleanish | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: React.CSSProperties | undefined; tabIndex?: number | undefined; translate?: \"no\" | \"yes\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; prefix?: string | undefined; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"none\" | \"email\" | \"search\" | \"text\" | \"tel\" | \"url\" | \"numeric\" | \"decimal\" | undefined; is?: string | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"date\" | \"location\" | \"time\" | \"page\" | \"false\" | \"true\" | \"step\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"none\" | \"copy\" | \"link\" | \"execute\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"grid\" | \"menu\" | \"false\" | \"true\" | \"dialog\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"false\" | \"true\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"all\" | \"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"other\" | \"ascending\" | \"descending\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; 'data-test-subj'?: string | undefined; height?: string | number | undefined; width?: string | undefined; readOnly?: boolean | undefined; align?: ", + "; headers?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; lang?: string | undefined; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; contentEditable?: \"inherit\" | Booleanish | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: React.CSSProperties | undefined; tabIndex?: number | undefined; translate?: \"no\" | \"yes\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; prefix?: string | undefined; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"none\" | \"email\" | \"search\" | \"text\" | \"tel\" | \"url\" | \"numeric\" | \"decimal\" | undefined; is?: string | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"date\" | \"location\" | \"time\" | \"page\" | \"false\" | \"true\" | \"step\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"none\" | \"copy\" | \"link\" | \"execute\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"grid\" | \"menu\" | \"false\" | \"true\" | \"dialog\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"false\" | \"true\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"all\" | \"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"other\" | \"ascending\" | \"descending\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; 'data-test-subj'?: string | undefined; css?: ", + "Interpolation", + "<", + "Theme", + ">; height?: string | number | undefined; width?: string | undefined; readOnly?: boolean | undefined; align?: ", "HorizontalAlignment", " | undefined; abbr?: string | undefined; footer?: string | React.ReactElement> | ((props: ", "EuiTableFooterProps", diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index e2f4168974e43..8f802bca47dd3 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: 2022-09-20 +date: 2022-09-22 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 a354225c86402..2ee5a06e9512f 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: 2022-09-20 +date: 2022-09-22 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 50182897c2fbd..d690ce3f16ec2 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: 2022-09-20 +date: 2022-09-22 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 df41afa0216df..613210385f877 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: 2022-09-20 +date: 2022-09-22 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 f3de4702d11b1..185e146c2db95 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: 2022-09-20 +date: 2022-09-22 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 7bcef95c29df0..51927a493f916 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 9f6f7650337cb..415b526e0b26c 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: 2022-09-20 +date: 2022-09-22 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 5827c20db016b..a67fa886963b7 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -64,7 +64,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly threatIntelligenceEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly threatIntelligenceEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, @@ -1080,6 +1080,20 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "securitySolution", + "id": "def-server.SecuritySolutionApiRequestHandlerContext.getQueryRuleAdditionalOptions", + "type": "Object", + "tags": [], + "label": "getQueryRuleAdditionalOptions", + "description": [], + "signature": [ + "CreateQueryRuleAdditionalOptions" + ], + "path": "x-pack/plugins/security_solution/server/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1095,7 +1109,7 @@ "label": "ConfigType", "description": [], "signature": [ - "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; threatIntelligenceEnabled: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; insightsRelatedAlertsByProcessAncestry: boolean; extendedRuleExecutionLoggingEnabled: boolean; socTrendsEnabled: boolean; }>; }" + "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; threatIntelligenceEnabled: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; insightsRelatedAlertsByProcessAncestry: boolean; extendedRuleExecutionLoggingEnabled: boolean; socTrendsEnabled: boolean; responseActionsEnabled: boolean; }>; }" ], "path": "x-pack/plugins/security_solution/server/config.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 5022716224980..15e420b2b0182 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Security solution](https://github.com/orgs/elastic/teams/security-solut | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 54 | 0 | 53 | 22 | +| 55 | 0 | 54 | 23 | ## Client diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 25ab4bd3d6c53..b4ef6d48c46bf 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: 2022-09-20 +date: 2022-09-22 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 017d32c37d4f7..b8e64fb777735 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index c6c6e0cc46148..594c05507252c 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 1ffb54e68ed96..07430e37fe600 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.devdocs.json b/api_docs/stack_alerts.devdocs.json index 209dcb93ef3a1..3850659e8a4d7 100644 --- a/api_docs/stack_alerts.devdocs.json +++ b/api_docs/stack_alerts.devdocs.json @@ -63,7 +63,7 @@ "signature": [ "\"stackAlerts\"" ], - "path": "x-pack/plugins/stack_alerts/common/index.ts", + "path": "x-pack/plugins/stack_alerts/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index d50a8500a951d..90bbd271763c1 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index dcbc38512094c..ed1fa872c8a04 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: 2022-09-20 +date: 2022-09-22 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 de40bcf02dc92..b3cb07fff0933 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: 2022-09-20 +date: 2022-09-22 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 fd689d3934388..b23d2d1888173 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: 2022-09-20 +date: 2022-09-22 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 f1d4653b9dc81..226591c004f88 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: 2022-09-20 +date: 2022-09-22 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 89a20be1cb5fa..08f903b0515f4 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: 2022-09-20 +date: 2022-09-22 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 b945f23a74e3a..75afad06122f6 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index d2593dcd4c562..c0d37bafaf0f7 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 4bd6794b21f76..12818ccc00767 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: 2022-09-20 +date: 2022-09-22 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 4868243b57664..2c93efb7ba04b 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: 2022-09-20 +date: 2022-09-22 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 77ded4f84172b..42809d2bac92c 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -2339,6 +2339,150 @@ "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.browserFields", + "type": "Any", + "tags": [], + "label": "browserFields", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.onToggleColumn", + "type": "Function", + "tags": [], + "label": "onToggleColumn", + "description": [], + "signature": [ + "(columnId: string) => void" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.onToggleColumn.$1", + "type": "string", + "tags": [], + "label": "columnId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.onResetColumns", + "type": "Function", + "tags": [], + "label": "onResetColumns", + "description": [], + "signature": [ + "() => void" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.onColumnsChange", + "type": "Function", + "tags": [], + "label": "onColumnsChange", + "description": [], + "signature": [ + "(columns: ", + "EuiDataGridColumn", + "[], visibleColumns: string[]) => void" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.onColumnsChange.$1", + "type": "Array", + "tags": [], + "label": "columns", + "description": [], + "signature": [ + "EuiDataGridColumn", + "[]" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.onColumnsChange.$2", + "type": "Array", + "tags": [], + "label": "visibleColumns", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.onChangeVisibleColumns", + "type": "Function", + "tags": [], + "label": "onChangeVisibleColumns", + "description": [], + "signature": [ + "(newColumns: string[]) => void" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.onChangeVisibleColumns.$1", + "type": "Array", + "tags": [], + "label": "newColumns", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false @@ -2766,8 +2910,14 @@ "A map of categoryId -> metadata about the fields in that category" ], "signature": [ - "{ readonly [x: string]: Partial<", - "BrowserField", + "{ [x: string]: Partial<", + { + "pluginId": "ruleRegistry", + "scope": "common", + "docId": "kibRuleRegistryPluginApi", + "section": "def-common.BrowserField", + "text": "BrowserField" + }, ">; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/types.ts", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index b81484ae1426f..9959fc8d580d4 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Response Ops](https://github.com/orgs/elastic/teams/response-ops) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 428 | 0 | 407 | 46 | +| 437 | 1 | 416 | 45 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 8b6617ccdb0c0..215ab9ab7a5a3 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.devdocs.json b/api_docs/ui_actions_enhanced.devdocs.json index f4fbda2b1815f..923b69011b532 100644 --- a/api_docs/ui_actions_enhanced.devdocs.json +++ b/api_docs/ui_actions_enhanced.devdocs.json @@ -2854,7 +2854,7 @@ "tags": [], "label": "getDisplayName", "description": [ - "\nShould return an internationalized name of the drilldown, which will be\ndisplayed to the user." + "\nShould return an internationalized name of the drilldown, which will be\ndisplayed to the user as the name of drilldown factory when configuring a drilldown." ], "signature": [ "() => string" @@ -2865,6 +2865,45 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "uiActionsEnhanced", + "id": "def-public.DrilldownDefinition.actionMenuItem", + "type": "Function", + "tags": [], + "label": "actionMenuItem", + "description": [ + "\nName of the drilldown instance displayed to the user at the moment of\ndrilldown execution. Should be internationalized." + ], + "signature": [ + { + "pluginId": "kibanaUtils", + "scope": "common", + "docId": "kibKibanaUtilsPluginApi", + "section": "def-common.UiComponent", + "text": "UiComponent" + }, + "<{ config: Omit<", + { + "pluginId": "uiActionsEnhanced", + "scope": "common", + "docId": "kibUiActionsEnhancedPluginApi", + "section": "def-common.SerializedAction", + "text": "SerializedAction" + }, + ", \"factoryId\">; context: ExecutionContext | ", + { + "pluginId": "uiActions", + "scope": "public", + "docId": "kibUiActionsPluginApi", + "section": "def-public.ActionExecutionContext", + "text": "ActionExecutionContext" + }, + "; }> | undefined" + ], + "path": "src/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "uiActionsEnhanced", "id": "def-public.DrilldownDefinition.isCompatible", diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index d9207909cfb57..bb4305309d371 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 205 | 0 | 142 | 9 | +| 206 | 0 | 142 | 9 | ## Client diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index f64afc9f9b339..117b0750b9d78 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index 4d5026322ffbb..89093ced7129d 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -366,7 +366,7 @@ }, " | undefined; textBasedLanguageModeErrors?: Error[] | undefined; onTextBasedSavedAndExit?: (({ onSave }: ", "OnSaveTextLanguageQueryProps", - ") => void) | undefined; showSubmitButton?: boolean | undefined; suggestionsSize?: ", + ") => void) | undefined; showSubmitButton?: boolean | undefined; submitButtonStyle?: \"auto\" | \"full\" | \"iconOnly\" | undefined; suggestionsSize?: ", "SuggestionsListSize", " | undefined; isScreenshotMode?: boolean | undefined; onFiltersUpdated?: ((filters: ", "Filter", @@ -1030,6 +1030,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.IUnifiedSearchPluginServices.docLinks", + "type": "Object", + "tags": [], + "label": "docLinks", + "description": [], + "signature": [ + "DocLinksStart" + ], + "path": "src/plugins/unified_search/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedSearch", "id": "def-public.IUnifiedSearchPluginServices.data", @@ -1470,6 +1484,31 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.QueryStringInputProps.appName", + "type": "string", + "tags": [], + "label": "appName", + "description": [], + "path": "src/plugins/unified_search/public/query_string_input/query_string_input.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.QueryStringInputProps.deps", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "QueryStringInputDependencies" + ], + "path": "src/plugins/unified_search/public/query_string_input/query_string_input.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedSearch", "id": "def-public.QueryStringInputProps.nonKqlMode", diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index eb27791b68225..69b4ac2504553 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-servic | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 122 | 2 | 96 | 16 | +| 125 | 2 | 99 | 17 | ## Client diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 307924444436a..929d32a836b61 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-servic | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 122 | 2 | 96 | 16 | +| 125 | 2 | 99 | 17 | ## Client diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 4718f11d57d79..ccf74575df120 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 474edc06dbf7c..a736d4d52eba8 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: 2022-09-20 +date: 2022-09-22 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 5ff9b1833cfb0..03f35c432a9f8 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: 2022-09-20 +date: 2022-09-22 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 63eab9d578d13..7f07f304187fc 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: 2022-09-20 +date: 2022-09-22 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 944b8314efe6d..ba53a8580fdff 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: 2022-09-20 +date: 2022-09-22 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 0971a99c4c319..46fbb7e99329f 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: 2022-09-20 +date: 2022-09-22 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 2e9c36c73eb9a..680635f94e279 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: 2022-09-20 +date: 2022-09-22 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 0c0d6e2b147ce..eae6da767fea5 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: 2022-09-20 +date: 2022-09-22 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 990c37c3170ae..7fdec24dd1ac8 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: 2022-09-20 +date: 2022-09-22 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 d7a2655939277..1de3ded2cdc39 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: 2022-09-20 +date: 2022-09-22 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 d0388d1f5e605..4c4ad09221dec 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: 2022-09-20 +date: 2022-09-22 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 851682c10efa5..ca416be51f23c 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: 2022-09-20 +date: 2022-09-22 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 de09fefa94f16..b8bc8c502fb16 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index 33b99385c989a..f9d7be383b81f 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -2546,6 +2546,26 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "visualizations", + "id": "def-public.EditorRenderProps.unifiedSearch", + "type": "Object", + "tags": [], + "label": "unifiedSearch", + "description": [], + "signature": [ + { + "pluginId": "unifiedSearch", + "scope": "public", + "docId": "kibUnifiedSearchPluginApi", + "section": "def-public.UnifiedSearchPublicPluginStart", + "text": "UnifiedSearchPublicPluginStart" + } + ], + "path": "src/plugins/visualizations/public/visualize_app/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "visualizations", "id": "def-public.EditorRenderProps.linked", @@ -7066,6 +7086,62 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.isAnnotationsLayer", + "type": "Function", + "tags": [], + "label": "isAnnotationsLayer", + "description": [], + "signature": [ + "(layer: Pick<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYLayerConfig", + "text": "XYLayerConfig" + }, + ", \"layerType\">) => layer is ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYAnnotationsLayerConfig", + "text": "XYAnnotationsLayerConfig" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.isAnnotationsLayer.$1", + "type": "Object", + "tags": [], + "label": "layer", + "description": [], + "signature": [ + "Pick<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYLayerConfig", + "text": "XYLayerConfig" + }, + ", \"layerType\">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.isVisDimension", @@ -7970,6 +8046,155 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig", + "type": "Interface", + "tags": [], + "label": "EventAnnotationConfig", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.filter", + "type": "CompoundType", + "tags": [], + "label": "filter", + "description": [], + "signature": [ + "{ type: \"kibana_query\"; } & ", + "Query" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.timeField", + "type": "string", + "tags": [], + "label": "timeField", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.extraFields", + "type": "Array", + "tags": [], + "label": "extraFields", + "description": [], + "signature": [ + "string[] | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.color", + "type": "string", + "tags": [], + "label": "color", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.isHidden", + "type": "CompoundType", + "tags": [], + "label": "isHidden", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.icon", + "type": "string", + "tags": [], + "label": "icon", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"query\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.EventAnnotationConfig.key", + "type": "Object", + "tags": [], + "label": "key", + "description": [], + "signature": [ + "{ type: \"point_in_time\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.Filter", @@ -9533,6 +9758,88 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYAnnotationsLayerConfig", + "type": "Interface", + "tags": [], + "label": "XYAnnotationsLayerConfig", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.XYAnnotationsLayerConfig.layerId", + "type": "string", + "tags": [], + "label": "layerId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYAnnotationsLayerConfig.annotations", + "type": "Array", + "tags": [], + "label": "annotations", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.EventAnnotationConfig", + "text": "EventAnnotationConfig" + }, + "[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYAnnotationsLayerConfig.ignoreGlobalFilters", + "type": "boolean", + "tags": [], + "label": "ignoreGlobalFilters", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYAnnotationsLayerConfig.layerType", + "type": "string", + "tags": [], + "label": "layerType", + "description": [], + "signature": [ + "\"annotations\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYAnnotationsLayerConfig.indexPatternId", + "type": "string", + "tags": [], + "label": "indexPatternId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.XYConfiguration", @@ -11742,6 +12049,14 @@ "docId": "kibVisualizationsPluginApi", "section": "def-common.XYReferenceLineLayerConfig", "text": "XYReferenceLineLayerConfig" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYAnnotationsLayerConfig", + "text": "XYAnnotationsLayerConfig" } ], "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 86e1618717ea1..4fd3e74a04cf9 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: 2022-09-20 +date: 2022-09-22 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 611 | 12 | 582 | 14 | +| 631 | 12 | 602 | 14 | ## Client diff --git a/dev_docs/operations/operations_landing.mdx b/dev_docs/operations/operations_landing.mdx index 88e4be9eb93a3..9004141255f58 100644 --- a/dev_docs/operations/operations_landing.mdx +++ b/dev_docs/operations/operations_landing.mdx @@ -26,6 +26,7 @@ layout: landing { pageId: "kibDevDocsOpsWritingStableFunctionalTests" }, { pageId: "kibDevDocsOpsFlakyTestRunner" }, { pageId: "kibDevDocsOpsCiStats" }, + { pageId: "kibDevDocsOpsJourneys" }, ]} /> diff --git a/docs/api/actions-and-connectors/execute.asciidoc b/docs/api/actions-and-connectors/execute.asciidoc index e477d7237c273..b5c59bb86bc70 100644 --- a/docs/api/actions-and-connectors/execute.asciidoc +++ b/docs/api/actions-and-connectors/execute.asciidoc @@ -437,6 +437,60 @@ the security incident. The IPs are added as observables to the security incident `message`:: (Required, string) The message to log. ===== + +.{swimlane} connectors +[%collapsible%open] +===== +`subAction`:: +(Required, string) The action to test. It must be `pushToService`. + +`subActionParams`:: +(Required, object) The set of configuration properties. ++ +.Properties of `subActionParams` +[%collapsible%open] +====== +`comments`::: +(Optional, array of objects) Additional information that is sent to {swimlane}. ++ +.Properties of `comments` objects +[%collapsible%open] +======= +comment:::: +(string) A comment related to the incident. For example, describe how to +troubleshoot the issue. + +commentId:::: +(integer) A unique identifier for the comment. + +======= + +`incident`::: +(Required, object) Information necessary to create or update a {swimlane} incident. ++ +.Properties of `incident` +[%collapsible%open] +======= +`alertId`:::: +(Optional, string) The alert identifier. + +`caseId`:::: +(Optional, string) The case identifier for the incident. + +`caseName`:::: +(Optional, string) The case name for the incident. + +`description`:::: +(Optional, string) The description of the incident. + +`ruleName`:::: +(Optional, string) The rule name. + +`severity`:::: +(Optional, string) The severity of the incident. +======= +====== +===== ==== -- @@ -549,6 +603,41 @@ The API returns the following: } -------------------------------------------------- +Create then update a {swimlane} incident: +[source,sh] +-------------------------------------------------- +POST api/actions/connector/a4746470-2f94-11ed-b0e0-87533c532698/_execute +{ + "params":{ + "subAction":"pushToService", + "subActionParams":{ + "incident":{ + "description":"Description of the incident", + "caseName":"Case name", + "caseId":"1000" + }, + "comments":[ + {"commentId":"1","comment":"A comment about the incident"} + ] + } + } +} + +POST api/actions/connector/a4746470-2f94-11ed-b0e0-87533c532698/_execute +{ + "params":{ + "subAction":"pushToService", + "subActionParams":{ + "incident":{ + "caseId":"1000", + "caseName":"A new case name" + } + } + } +} +-------------------------------------------------- +// KIBANA + Retrieve the list of choices for a {sn-itom} connector: [source,sh] @@ -583,4 +672,5 @@ The API returns the severity and urgency choices, for example: {"dependent_value":"","label":"3 - Low","value":"3","element":"urgency"}], "connector_id":"9d9be270-2fd2-11ed-b0e0-87533c532698" } --------------------------------------------------- \ No newline at end of file +-------------------------------------------------- + diff --git a/docs/developer/contributing/development-functional-tests.asciidoc b/docs/developer/contributing/development-functional-tests.asciidoc index 2d47ad92582bb..cf854c6d53cfa 100644 --- a/docs/developer/contributing/development-functional-tests.asciidoc +++ b/docs/developer/contributing/development-functional-tests.asciidoc @@ -31,18 +31,23 @@ There are three ways to run the tests depending on your goals: 3. Custom option: ** Description: Runs tests against instances of {es} & {kib} started some other way (like Elastic Cloud, or an instance you are managing in some other way). -** just executes the functional tests -** url, credentials, etc. for {es} and {kib} are specified via environment variables -** Here's an example that runs against an Elastic Cloud instance. Note that you must run the same branch of tests as the version of {kib} you're testing. +** Just executes the functional tests +** URL, credentials, etc. for {es} and {kib} are specified via environment variables +** When running against an Elastic Cloud instance, additional environment variables are required `TEST_CLOUD` and `ES_SECURITY_ENABLED` +** You must run the same branch of tests as the version of {kib} you're testing. To run against a previous minor version use option `--es-version ` +** To run a specific configuration use option `--config ` +** Here's an example that runs against an Elastic Cloud instance + ["source","shell"] ---------- -export TEST_KIBANA_URL=https://kibana:password@my-kibana-instance.internal.net:443 +export TEST_KIBANA_URL=https://elastic:password@my-kbn-cluster.elastic-cloud.com:443 +export TEST_ES_URL=https://elastic:password@my-es-cluster.elastic-cloud.com:443 -export TEST_ES_URL=https://elastic:password@my-es-cluster.internal.net:9200 -node scripts/functional_test_runner ----------- +export TEST_CLOUD=1 +export ES_SECURITY_ENABLED=1 +node scripts/functional_test_runner [--config ] [--es-version ] +---------- ** Or you can override any or all of these individual parts of the URL and leave the others to the default values. + @@ -527,4 +532,4 @@ If your functional tests are flaky then the Operations team might skip them and This will take you to Buildkite where your build will run and tell you if it failed in any execution. -A flaky test may only fail once in 1000 runs, so keep this in mind and make sure you use enough executions to really prove that a test isn't flaky anymore. \ No newline at end of file +A flaky test may only fail once in 1000 runs, so keep this in mind and make sure you use enough executions to really prove that a test isn't flaky anymore. diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 9fd9bf84f09d5..e84cc4c4ee0b0 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -177,7 +177,7 @@ for use in their own application. |{kib-repo}blob/{branch}/src/plugins/guided_onboarding/README.md[guidedOnboarding] -|A Kibana plugin +|This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages. |{kib-repo}blob/{branch}/src/plugins/home/README.md[home] @@ -424,6 +424,10 @@ The plugin exposes the static DefaultEditorController class to consume. |The cloud plugin adds Cloud-specific features to Kibana. +|{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_experiments/README.mdx[cloudExperiments] +|The Cloud Experiments Service provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments. + + |{kib-repo}blob/{branch}/x-pack/plugins/cloud_security_posture/README.md[cloudSecurityPosture] |Cloud Posture automates the identification and remediation of risks across cloud infrastructures diff --git a/docs/discover/document-explorer.asciidoc b/docs/discover/document-explorer.asciidoc index 32811cfbe7728..9547dbaf4477f 100644 --- a/docs/discover/document-explorer.asciidoc +++ b/docs/discover/document-explorer.asciidoc @@ -3,23 +3,11 @@ *Discover* displays your documents in table format, so you can -best explore your data. -Use the document table to resize columns, set row height, +best explore your data. Resize columns, set row height, perform multi-column sorting, compare data, and more. -++++ - - -
-++++ +[role="screenshot"] +image:images/customer.png[Customer last name, first initial in the document table] [float] [[document-explorer-columns]] diff --git a/docs/management/managing-saved-objects.asciidoc b/docs/management/managing-saved-objects.asciidoc index ee1247501e8da..7fccd6c6c93f7 100644 --- a/docs/management/managing-saved-objects.asciidoc +++ b/docs/management/managing-saved-objects.asciidoc @@ -1,11 +1,10 @@ [[managing-saved-objects]] -== Saved Objects +== Manage saved objects -The *Saved Objects* UI helps you keep track of and manage your saved objects. These objects -store data for later use, including dashboards, visualizations, maps, data views, -Canvas workpads, and more. +Edit, import, export, and copy your saved objects. These objects include +dashboards, visualizations, maps, {data-sources}, *Canvas* workpads, and other saved objects. -To get started, open the main menu, then click *Stack Management > Saved Objects*. +To get started, open the main menu, and then click *Stack Management > Saved Objects*. [role="screenshot"] image::images/management-saved-objects.png[Saved Objects] @@ -13,23 +12,24 @@ image::images/management-saved-objects.png[Saved Objects] [float] === Required permissions -The `Saved Objects Management` {kib} privilege is required to access the *Saved Objects* UI. +To access *Saved Objects*, you must have the required `Saved Objects Management` {kib} privilege. -To add the privilege, open the menu, then click *Stack Management > Roles*. +To add the privilege, open the main menu, and then click *Stack Management > Roles*. -NOTE: -Granting access to Saved Objects Management will authorize users to manage all saved objects in {kib}, including objects that are managed by applications they may not otherwise be authorized to access. +NOTE: Granting access to `Saved Objects Management` authorizes users to +manage all saved objects in {kib}, including objects that are managed by +applications they may not otherwise be authorized to access. [float] [[managing-saved-objects-view]] === View and delete -* To view and edit an object in its associated application, click the object title. +* To view and edit a saved object in its associated application, click the object title. * To show objects that use this object, so you know the impact of deleting it, click the actions icon image:images/actions_icon.png[Actions icon] -and select *Relationships*. +and then select *Relationships*. * To delete one or more objects, select their checkboxes, and then click *Delete*. @@ -37,58 +37,67 @@ and select *Relationships*. [[managing-saved-objects-export-objects]] === Import and export -Using the import and export actions, you can move objects between different -{kib} instances. This action is useful when you -have multiple environments for development and production. -Import and export also work well when you have a large number -of objects to update and want to batch the process. +Use import and export to move objects between different {kib} instances. +These actions are useful when you have multiple environments for development and production. +Import and export also work well when you have a large number of objects to update and want to batch the process. -In addition to the user interface, {kib} provides beta <> and <> APIs if -you want to automate this process. +{kib} also provides <> and +<> APIs to automate this process. -[float] -==== Compatibility across versions - -With each release, {kib} introduces changes to the way saved objects are stored. When importing a saved object, {kib} will run the necessary migrations to ensure that the imported saved objects are compatible with the current version. - -However, saved objects can only be imported 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 {kib}. See the table below for compatibility examples: - -|======= -| Exporting version | Importing version | Compatible? -| 6.7.0 | 6.8.1 | Yes -| 6.8.1 | 7.3.0 | Yes -| 7.3.0 | 7.11.1 | Yes -| 7.11.1 | 7.6.0 | No -| 6.8.1 | 8.0.0 | No -|======= [float] ==== Import -You can import multiple objects in a single operation. Click *Import* and -navigate to the NDJSON file that -represents the objects to import. By default, +Import multiple objects in a single operation. + +. In the toolbar, click *Import*. +. Select the NDJSON file that +includes the objects you want to import. +. Select the import options. By default, saved objects already in {kib} are overwritten. +. Click *Import*. NOTE: The <> configuration setting -limits the number of saved objects which may be included in this file. Similarly, the +limits the number of saved objects to include in the file. The <> setting limits the overall -size of the file that can be imported. +size of the file that you can import. [float] ==== Export -You have two options for exporting saved objects. +Export objects by selection or type. -* Select the checkboxes of objects that you want to export, and then click *Export*. -* Click *Export x objects*, and export objects by type. +* To export specific objects, select them in the table, and then click *Export*. +* To export objects by type, click *Export objects* in the toolbar. -This action creates an NDJSON with all your saved objects. By default, the NDJSON includes child objects that are related to the saved -objects. Exported dashboards include their associated data views. +{kib} creates an NDJSON with all your saved objects. By default, the NDJSON includes child objects related to the saved +objects. Exported dashboards include their associated {data-sources}. NOTE: The <> configuration setting -limits the number of saved objects which may be exported. +limits the number of saved objects that you can export. + +[float] +==== Compatibility across versions + +With each release, {kib} introduces changes to the way saved objects are stored. +When importing a saved object, {kib} runs the necessary migrations to ensure +that the imported saved objects are compatible with the current version. + +However, saved objects can only be imported into the same version, +a newer minor on the same major, or the next major. +Exported saved objects are not backward compatible and cannot be imported +into an older version of {kib}. For example: + +|======= +| Exporting version | Importing version | Compatible? +| 6.7.0 | 6.8.1 | Yes +| 6.8.1 | 7.3.0 | Yes +| 7.3.0 | 7.11.1 | Yes +| 7.11.1 | 7.6.0 | No +| 6.8.1 | 8.0.0 | No +|======= + [float] @@ -96,12 +105,16 @@ limits the number of saved objects which may be exported. [[managing-saved-objects-copy-to-space]] === Copy to other {kib} spaces -To copy a saved object to another space, click the actions icon image:images/actions_icon.png[Actions icon] -and select *Copy to spaces*. From here, you can select the spaces in which to copy the object. -You can also select whether to automatically overwrite any conflicts in the target spaces, or -resolve them manually. +Copy saved objects and their related objects between spaces. -WARNING: The copy operation automatically includes child objects that are related to the saved objects. If you don't want this behavior, use +. Click the actions icon image:images/actions_icon.png[Actions icon]. +. Click *Copy to spaces*. +. Select the spaces in which to copy the object. +. Specify whether to automatically overwrite any objects that already exist +in the target spaces, or resolve them on a per-object basis. ++ +The copy operation automatically includes child objects that are related to +the saved object. If you don't want this behavior, use the <> instead. [float] @@ -109,13 +122,18 @@ the <> instead. [[managing-saved-objects-share-to-space]] === Share to other {kib} spaces -To share a saved object to another space -- which makes a single saved object available in multiple spaces -- click the actions icon -image:images/actions_icon.png[Actions icon] and select *Share to spaces*. From here, you can select the spaces in which to share the object, -or indicate that you want the object to be shared to _all spaces_, which includes those that exist now and any created in the future. +Make a single saved object available in multiple spaces. -Not all saved object types are shareable. If an object is shareable, the Spaces column shows which spaces it exists in. You can also click +. Click the actions icon +image:images/actions_icon.png[Actions icon]. +. Select *Share to spaces*. +. Select the spaces in which to share the object. +Or, indicate that you want the object to be shared to _all spaces_, +which includes those that exist now and any created in the future. ++ +Not all saved object types are shareable. If an object is shareable, the *Spaces* column shows where the object exists. You can click those space icons to open the Share UI. - -WARNING: The share operation automatically includes child objects that are related to the saved objects. ++ +The share operation automatically includes child objects that are related to the saved objects. include::saved-objects/saved-object-ids.asciidoc[] diff --git a/docs/management/managing-tags.asciidoc b/docs/management/managing-tags.asciidoc index a0b3dce7f4b27..b9fbe85760786 100644 --- a/docs/management/managing-tags.asciidoc +++ b/docs/management/managing-tags.asciidoc @@ -2,8 +2,10 @@ [[managing-tags]] == Tags -Tags enable you to categorize your saved objects. -You can then filter for related objects based on shared tags. +Use tags to categorize your saved objects, +then filter for related objects based on shared tags. + +To get started, open the main menu, and then click *Stack Management > Tags*. [role="screenshot"] image::images/tags/tag-management-section.png[Tags management] @@ -29,7 +31,6 @@ from the global search. Create a tag to assign to your saved objects. -. Open the main menu, and then click *Stack Management > Tags*. . Click *Create tag*. . Enter a name and select a color for the new tag. @@ -41,23 +42,21 @@ The name cannot be longer than 50 characters. [[settings-assign-tag]] === Assign a tag to an object -To assign and remove tags from saved objects, you must have `write` permission +To assign and remove tags, you must have `write` permission on the objects to which you assign the tags. -. In the *Tags* view, find the tag you want to assign. -. Click the action menu (...) in the tag row, -and then select the *Manage assignments* action. +. Find the tag you want to assign. +. Click the actions icon +image:images/actions_icon.png[Actions icon], +and then select *Manage assignments*. . Select the objects to which you want to assign or remove tags. + [role="screenshot"] -image::images/tags/manage-assignments-flyout.png[Assign flyout] +image::images/tags/manage-assignments-flyout.png[Assign flyout, width=75%] . Click *Save tag assignments*. -TIP: To assign, delete, or clear multiple tags at once, -select their checkboxes in the *Tags* view, and then select -the desired action from the *selected tags* menu. [float] [[settings-delete-tag]] @@ -65,6 +64,11 @@ the desired action from the *selected tags* menu. When you delete a tag, you remove it from all saved objects that use it. -. Click the action menu (...) in the tag row, and then select the *Delete* action. +. Click the actions icon +image:images/actions_icon.png[Actions icon], and then select *Delete*. . Click *Delete tag*. + +TIP: To assign, delete, or clear multiple tags, +select them in the *Tags* view, and then select +the action from the *selected tags* menu. diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index 5e87efc5e8aca..03274bec76714 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -95,7 +95,7 @@ a field, select *Edit Settings*, and change *Terms per hop*. Documents that match a blocked term are not allowed in the graph. To block a term, select its vertex and click the block icon -image:user/graph/images/graph-block-button.png[Block selection] +image:user/graph/images/graph-block-button.png[Block list] in the control panel. For a list of blocked terms, go to *Settings > Blocked terms*. diff --git a/docs/user/whats-new.asciidoc b/docs/user/whats-new.asciidoc index 640a824180480..399de14d5f18c 100644 --- a/docs/user/whats-new.asciidoc +++ b/docs/user/whats-new.asciidoc @@ -1,8 +1,12 @@ [[whats-new]] -== What's new in 8.0 +== What's new in {minor-version} -This section summarizes the most important changes in each release. For the -full list, see <> and <>. +Here are the highlights of what's new and improved in {minor-version}. +For detailed information about this release, +check the <>. + +Previous versions: {kibana-ref-all}/8.4/whats-new.html[8.4] | {kibana-ref-all}/8.3/whats-new.html[8.3] | {kibana-ref-all}/8.2/whats-new.html[8.2] +| {kibana-ref-all}/8.1/whats-new.html[8.1] | {kibana-ref-all}/8.0/whats-new.html[8.0] //NOTE: The notable-highlights tagged regions are re-used in the //Installation and Upgrade Guide diff --git a/examples/guided_onboarding_example/README.md b/examples/guided_onboarding_example/README.md index c00f5c7a4656c..544db5b5731e8 100755 --- a/examples/guided_onboarding_example/README.md +++ b/examples/guided_onboarding_example/README.md @@ -1,6 +1,6 @@ # guidedOnboardingExample -A Kibana plugin +This plugin contains code examples for the Guided Onboarding plugin. More information can be found in `KIBANA_FOLDER/src/plugins/guided_onboarding/README.md` --- diff --git a/examples/guided_onboarding_example/public/components/main.tsx b/examples/guided_onboarding_example/public/components/main.tsx index 8c372bd066271..157b13f1276c0 100644 --- a/examples/guided_onboarding_example/public/components/main.tsx +++ b/examples/guided_onboarding_example/public/components/main.tsx @@ -10,9 +10,10 @@ import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import { CoreStart } from '@kbn/core/public'; import { EuiButton, - EuiFieldNumber, + EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -29,7 +30,6 @@ import { GuidedOnboardingState, UseCase, } from '@kbn/guided-onboarding-plugin/public'; -import { CoreStart } from '@kbn/core/public'; interface MainProps { guidedOnboarding: GuidedOnboardingPluginStart; @@ -51,9 +51,11 @@ export const Main = (props: MainProps) => { ); useEffect(() => { - const subscription = guidedOnboardingApi?.fetchGuideState$().subscribe((newState) => { - setGuideState(newState); - }); + const subscription = guidedOnboardingApi + ?.fetchGuideState$() + .subscribe((newState: GuidedOnboardingState) => { + setGuideState(newState); + }); return () => subscription?.unsubscribe(); }, [guidedOnboardingApi]); @@ -208,7 +210,7 @@ export const Main = (props: MainProps) => { - setSelectedStep(e.target.value)} /> diff --git a/examples/guided_onboarding_example/public/components/step_one.tsx b/examples/guided_onboarding_example/public/components/step_one.tsx index 65b4d8f1f4ad9..bacb43ad0f67a 100644 --- a/examples/guided_onboarding_example/public/components/step_one.tsx +++ b/examples/guided_onboarding_example/public/components/step_one.tsx @@ -18,6 +18,8 @@ import { EuiSpacer, } from '@elastic/eui'; +import useObservable from 'react-use/lib/useObservable'; + import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types'; interface GuidedOnboardingExampleAppDeps { @@ -28,17 +30,14 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => const { guidedOnboardingApi } = guidedOnboarding; const [isTourStepOpen, setIsTourStepOpen] = useState(false); - useEffect(() => { - const subscription = guidedOnboardingApi?.fetchGuideState$().subscribe((newState) => { - const { activeGuide: guide, activeStep: step } = newState; - - if (guide === 'search' && step === 'add_data') { - setIsTourStepOpen(true); - } - }); - return () => subscription?.unsubscribe(); - }, [guidedOnboardingApi]); + const isTourActive = useObservable( + guidedOnboardingApi!.isGuideStepActive$('search', 'add_data'), + false + ); + useEffect(() => { + setIsTourStepOpen(isTourActive); + }, [isTourActive]); return ( <> @@ -79,10 +78,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => > { - await guidedOnboardingApi?.updateGuideState({ - activeGuide: 'search', - activeStep: 'search_experience', - }); + await guidedOnboardingApi?.completeGuideStep('search', 'add_data'); }} > Complete step 1 diff --git a/examples/guided_onboarding_example/public/components/step_two.tsx b/examples/guided_onboarding_example/public/components/step_two.tsx index d5962dc5e1cd9..9f96532450bfc 100644 --- a/examples/guided_onboarding_example/public/components/step_two.tsx +++ b/examples/guided_onboarding_example/public/components/step_two.tsx @@ -80,10 +80,7 @@ export const StepTwo = (props: StepTwoProps) => { > { - await guidedOnboardingApi?.updateGuideState({ - activeGuide: 'search', - activeStep: 'optimize', - }); + await guidedOnboardingApi?.completeGuideStep('search', 'browse_docs'); }} > Complete step 2 diff --git a/package.json b/package.json index 8b52f5730c34b..7c8a27447d4c0 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dashboarding" ], "private": true, - "version": "8.5.0", + "version": "8.6.0", "branch": "main", "types": "./kibana.d.ts", "tsdocMetadata": "./build/tsdoc-metadata.json", @@ -105,11 +105,11 @@ "@dnd-kit/utilities": "^2.0.0", "@elastic/apm-rum": "^5.12.0", "@elastic/apm-rum-react": "^1.4.2", - "@elastic/charts": "48.0.1", + "@elastic/charts": "49.0.0", "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.3.0-canary.1", "@elastic/ems-client": "8.3.3", - "@elastic/eui": "63.0.6", + "@elastic/eui": "64.0.5", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", @@ -416,6 +416,7 @@ "@turf/distance": "6.0.1", "@turf/helpers": "6.0.1", "@turf/length": "^6.0.2", + "@types/byte-size": "^8.1.0", "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "antlr4ts": "^0.5.0-alpha.3", @@ -424,6 +425,7 @@ "base64-js": "^1.3.1", "bitmap-sdf": "^1.0.3", "brace": "0.11.1", + "byte-size": "^8.1.0", "canvg": "^3.0.9", "cbor-x": "^1.3.3", "chalk": "^4.1.0", @@ -442,14 +444,14 @@ "cytoscape": "^3.10.0", "cytoscape-dagre": "^2.2.2", "d3": "3.5.17", - "d3-array": "1.2.4", + "d3-array": "2.12.1", "d3-brush": "^3.0.0", "d3-cloud": "1.2.5", "d3-interpolate": "^3.0.1", - "d3-scale": "^2.2.2", + "d3-scale": "^3.3.0", "d3-selection": "^3.0.0", - "d3-shape": "^1.1.0", - "d3-time": "^1.1.0", + "d3-shape": "^2.1.0", + "d3-time": "^2.1.1", "d3-transition": "^3.0.1", "dedent": "^0.7.0", "deep-freeze-strict": "^1.1.1", @@ -503,6 +505,8 @@ "jsonwebtoken": "^8.3.0", "jsts": "^1.6.2", "kea": "^2.4.2", + "launchdarkly-js-client-sdk": "^2.22.1", + "launchdarkly-node-server-sdk": "^6.4.2", "load-json-file": "^6.2.0", "lodash": "^4.17.21", "lru-cache": "^4.1.5", @@ -591,6 +595,7 @@ "redux-saga": "^1.1.3", "redux-thunk": "^2.4.1", "redux-thunks": "^1.0.0", + "remark-gfm": "1.0.0", "remark-parse": "^8.0.3", "remark-stringify": "^8.0.3", "require-in-the-middle": "^5.2.0", @@ -632,7 +637,6 @@ "whatwg-fetch": "^3.0.0", "xml2js": "^0.4.22", "xterm": "^4.18.0", - "xterm-addon-fit": "^0.5.0", "yauzl": "^2.10.0", "yazl": "^2.5.1" }, @@ -698,11 +702,15 @@ "@kbn/eslint-plugin-eslint": "link:bazel-bin/packages/kbn-eslint-plugin-eslint", "@kbn/eslint-plugin-imports": "link:bazel-bin/packages/kbn-eslint-plugin-imports", "@kbn/expect": "link:bazel-bin/packages/kbn-expect", + "@kbn/failed-test-reporter-cli": "link:bazel-bin/packages/kbn-failed-test-reporter-cli", "@kbn/find-used-node-modules": "link:bazel-bin/packages/kbn-find-used-node-modules", + "@kbn/ftr-common-functional-services": "link:bazel-bin/packages/kbn-ftr-common-functional-services", + "@kbn/ftr-screenshot-filename": "link:bazel-bin/packages/kbn-ftr-screenshot-filename", "@kbn/generate": "link:bazel-bin/packages/kbn-generate", "@kbn/get-repo-files": "link:bazel-bin/packages/kbn-get-repo-files", "@kbn/import-resolver": "link:bazel-bin/packages/kbn-import-resolver", "@kbn/jest-serializers": "link:bazel-bin/packages/kbn-jest-serializers", + "@kbn/journeys": "link:bazel-bin/packages/kbn-journeys", "@kbn/kibana-manifest-schema": "link:bazel-bin/packages/kbn-kibana-manifest-schema", "@kbn/managed-vscode-config": "link:bazel-bin/packages/kbn-managed-vscode-config", "@kbn/managed-vscode-config-cli": "link:bazel-bin/packages/kbn-managed-vscode-config-cli", @@ -770,14 +778,13 @@ "@types/compression-webpack-plugin": "^2.0.2", "@types/cytoscape": "^3.14.0", "@types/d3": "^3.5.43", - "@types/d3-array": "^1.2.7", + "@types/d3-array": "^2.12.1", "@types/d3-brush": "^3.0.0", - "@types/d3-interpolate": "^2.0.0", - "@types/d3-scale": "^2.2.6", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^3.3.0", "@types/d3-selection": "^3.0.0", - "@types/d3-shape": "^1.3.1", - "@types/d3-time": "^1.0.10", - "@types/d3-time-format": "^2.1.1", + "@types/d3-shape": "^2.1.0", + "@types/d3-time": "^2.1.1", "@types/d3-transition": "^3.0.1", "@types/dagre": "^0.7.47", "@types/dedent": "^0.7.0", @@ -1014,8 +1021,11 @@ "@types/kbn__es-types": "link:bazel-bin/packages/kbn-es-types/npm_module_types", "@types/kbn__eslint-plugin-disable": "link:bazel-bin/packages/kbn-eslint-plugin-disable/npm_module_types", "@types/kbn__eslint-plugin-imports": "link:bazel-bin/packages/kbn-eslint-plugin-imports/npm_module_types", + "@types/kbn__failed-test-reporter-cli": "link:bazel-bin/packages/kbn-failed-test-reporter-cli/npm_module_types", "@types/kbn__field-types": "link:bazel-bin/packages/kbn-field-types/npm_module_types", "@types/kbn__find-used-node-modules": "link:bazel-bin/packages/kbn-find-used-node-modules/npm_module_types", + "@types/kbn__ftr-common-functional-services": "link:bazel-bin/packages/kbn-ftr-common-functional-services/npm_module_types", + "@types/kbn__ftr-screenshot-filename": "link:bazel-bin/packages/kbn-ftr-screenshot-filename/npm_module_types", "@types/kbn__generate": "link:bazel-bin/packages/kbn-generate/npm_module_types", "@types/kbn__get-repo-files": "link:bazel-bin/packages/kbn-get-repo-files/npm_module_types", "@types/kbn__handlebars": "link:bazel-bin/packages/kbn-handlebars/npm_module_types", @@ -1029,6 +1039,7 @@ "@types/kbn__interpreter": "link:bazel-bin/packages/kbn-interpreter/npm_module_types", "@types/kbn__io-ts-utils": "link:bazel-bin/packages/kbn-io-ts-utils/npm_module_types", "@types/kbn__jest-serializers": "link:bazel-bin/packages/kbn-jest-serializers/npm_module_types", + "@types/kbn__journeys": "link:bazel-bin/packages/kbn-journeys/npm_module_types", "@types/kbn__kbn-ci-stats-performance-metrics": "link:bazel-bin/packages/kbn-kbn-ci-stats-performance-metrics/npm_module_types", "@types/kbn__kibana-manifest-schema": "link:bazel-bin/packages/kbn-kibana-manifest-schema/npm_module_types", "@types/kbn__logging": "link:bazel-bin/packages/kbn-logging/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 051d0ac9bf27f..d6994772c9ef2 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -204,9 +204,12 @@ filegroup( "//packages/kbn-eslint-plugin-eslint:build", "//packages/kbn-eslint-plugin-imports:build", "//packages/kbn-expect:build", + "//packages/kbn-failed-test-reporter-cli:build", "//packages/kbn-field-types:build", "//packages/kbn-find-used-node-modules:build", "//packages/kbn-flot-charts:build", + "//packages/kbn-ftr-common-functional-services:build", + "//packages/kbn-ftr-screenshot-filename:build", "//packages/kbn-generate:build", "//packages/kbn-get-repo-files:build", "//packages/kbn-handlebars:build", @@ -217,6 +220,7 @@ filegroup( "//packages/kbn-interpreter:build", "//packages/kbn-io-ts-utils:build", "//packages/kbn-jest-serializers:build", + "//packages/kbn-journeys:build", "//packages/kbn-kibana-manifest-schema:build", "//packages/kbn-logging:build", "//packages/kbn-logging-mocks:build", @@ -281,6 +285,7 @@ filegroup( "//packages/kbn-utils:build", "//packages/kbn-yarn-lock-validator:build", "//packages/shared-ux/avatar/solution:build", + "//packages/shared-ux/avatar/user_profile/impl:build", "//packages/shared-ux/button_toolbar:build", "//packages/shared-ux/button/exit_full_screen/impl:build", "//packages/shared-ux/button/exit_full_screen/mocks:build", @@ -310,6 +315,9 @@ filegroup( "//packages/shared-ux/prompt/no_data_views/impl:build", "//packages/shared-ux/prompt/no_data_views/mocks:build", "//packages/shared-ux/prompt/no_data_views/types:build", + "//packages/shared-ux/router/impl:build", + "//packages/shared-ux/router/mocks:build", + "//packages/shared-ux/router/types:build", "//packages/shared-ux/storybook/config:build", "//packages/shared-ux/storybook/mock:build", "//x-pack/packages/ml/agg_utils:build", @@ -510,8 +518,11 @@ filegroup( "//packages/kbn-es-types:build_types", "//packages/kbn-eslint-plugin-disable:build_types", "//packages/kbn-eslint-plugin-imports:build_types", + "//packages/kbn-failed-test-reporter-cli:build_types", "//packages/kbn-field-types:build_types", "//packages/kbn-find-used-node-modules:build_types", + "//packages/kbn-ftr-common-functional-services:build_types", + "//packages/kbn-ftr-screenshot-filename:build_types", "//packages/kbn-generate:build_types", "//packages/kbn-get-repo-files:build_types", "//packages/kbn-handlebars:build_types", @@ -522,6 +533,7 @@ filegroup( "//packages/kbn-interpreter:build_types", "//packages/kbn-io-ts-utils:build_types", "//packages/kbn-jest-serializers:build_types", + "//packages/kbn-journeys:build_types", "//packages/kbn-kibana-manifest-schema:build_types", "//packages/kbn-logging:build_types", "//packages/kbn-logging-mocks:build_types", @@ -581,6 +593,7 @@ filegroup( "//packages/kbn-utils:build_types", "//packages/kbn-yarn-lock-validator:build_types", "//packages/shared-ux/avatar/solution:build_types", + "//packages/shared-ux/avatar/user_profile/impl:build_types", "//packages/shared-ux/button_toolbar:build_types", "//packages/shared-ux/button/exit_full_screen/impl:build_types", "//packages/shared-ux/button/exit_full_screen/mocks:build_types", @@ -601,6 +614,8 @@ filegroup( "//packages/shared-ux/page/solution_nav:build_types", "//packages/shared-ux/prompt/no_data_views/impl:build_types", "//packages/shared-ux/prompt/no_data_views/mocks:build_types", + "//packages/shared-ux/router/impl:build_types", + "//packages/shared-ux/router/mocks:build_types", "//packages/shared-ux/storybook/config:build_types", "//packages/shared-ux/storybook/mock:build_types", "//x-pack/packages/ml/agg_utils:build_types", diff --git a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts index 2609859ae9d8c..6e21336bbb6fe 100644 --- a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts +++ b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts @@ -22,16 +22,26 @@ describe('AnalyticsService', () => { expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(1); await expect( await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[0][0].context$) - ).toMatchInlineSnapshot(` + ).toMatchInlineSnapshot( + { + branch: expect.any(String), + buildNum: 9007199254740991, + buildSha: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + isDev: true, + isDistributable: false, + version: expect.any(String), + }, + ` Object { - "branch": "main", + "branch": Any, "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "isDev": true, "isDistributable": false, - "version": "8.5.0", + "version": Any, } - `); + ` + ); }); test('should register the `performance_metric` event type on creation', () => { diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts index b8b218db3d8fd..811d9d95831ef 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts @@ -51,7 +51,7 @@ describe('AgentManager', () => { expect(HttpAgent).toBeCalledTimes(1); expect(HttpAgent).toBeCalledWith({ keepAlive: true, - keepAliveMsecs: 50000, + keepAliveMsecs: 1000, maxFreeSockets: 256, maxSockets: 256, scheduling: 'lifo', @@ -68,7 +68,7 @@ describe('AgentManager', () => { expect(HttpAgent).toBeCalledTimes(1); expect(HttpAgent).toBeCalledWith({ keepAlive: true, - keepAliveMsecs: 50000, + keepAliveMsecs: 1000, maxFreeSockets: 32, maxSockets: 1024, scheduling: 'fifo', diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts index 237f6964d975b..eb68014561d77 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts @@ -13,7 +13,7 @@ import { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch'; const HTTPS = 'https:'; const DEFAULT_CONFIG: HttpAgentOptions = { keepAlive: true, - keepAliveMsecs: 50000, + keepAliveMsecs: 1000, maxSockets: 256, maxFreeSockets: 256, scheduling: 'lifo', diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/index.ts b/packages/core/saved-objects/core-saved-objects-api-browser/index.ts index 9fc3e6a78c5c0..e78c56d76556c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/index.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/index.ts @@ -22,4 +22,7 @@ export type { SavedObjectsBulkUpdateOptions, SavedObjectsBulkResolveResponse, SavedObjectsBulkCreateObject, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteResponseItem, + SavedObjectsBulkDeleteResponse, } from './src/apis'; diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts new file mode 100644 index 0000000000000..1e4b5d2268dea --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.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 { SavedObjectError } from '@kbn/core-saved-objects-common'; + +/** @public */ +export interface SavedObjectsBulkDeleteOptions { + force?: boolean; +} + +/** @public */ +export interface SavedObjectsBulkDeleteResponseItem { + id: string; + type: string; + success: boolean; + error?: SavedObjectError; +} + +/** @public */ +export interface SavedObjectsBulkDeleteResponse { + statuses: SavedObjectsBulkDeleteResponseItem[]; +} diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/index.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/index.ts index 4652facb972cc..afee0a01494e1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/index.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/index.ts @@ -19,3 +19,8 @@ export type { } from './find'; export type { ResolvedSimpleSavedObject } from './resolve'; export type { SavedObjectsUpdateOptions } from './update'; +export type { + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteResponseItem, + SavedObjectsBulkDeleteResponse, +} from './bulk_delete'; diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts index 123b5c81d4064..d222770a8579d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts @@ -19,7 +19,10 @@ import type { SavedObjectsFindOptions, SavedObjectsUpdateOptions, SavedObjectsDeleteOptions, + SavedObjectsBulkDeleteResponse, + SavedObjectsBulkDeleteOptions, } from './apis'; + import type { SimpleSavedObject } from './simple_saved_object'; /** @@ -52,6 +55,17 @@ export interface SavedObjectsClientContract { */ delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + /** + * Deletes multiple documents at once + * @param objects - an array of objects containing id, type + * @param options - optional force argument to force deletion of objects in a namespace other than the scoped client + * @returns The bulk delete result for the saved objects for the given types and ids. + */ + bulkDelete( + objects: SavedObjectTypeIdTuple[], + options?: SavedObjectsBulkDeleteOptions + ): Promise; + /** * Search for objects * diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/aggs_types/bucket_aggs.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/aggs_types/bucket_aggs.ts index 7c14da7e4b421..7a6d94c31f291 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/aggs_types/bucket_aggs.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/aggs_types/bucket_aggs.ts @@ -65,9 +65,14 @@ const existsSchema = s.object({ // For more details see how the types are defined in the elasticsearch javascript client: // https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295 const boolSchema = s.object({ - bool: s.object({ - must_not: s.oneOf([termSchema]), - }), + bool: s.oneOf([ + s.object({ + must_not: s.oneOf([termSchema, existsSchema]), + }), + s.object({ + filter: s.oneOf([termSchema, existsSchema]), + }), + ]), }); const orderSchema = s.oneOf([ diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index c86945c6acb5c..0739c9acab8f5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -46,6 +46,8 @@ import type { SavedObjectsCollectMultiNamespaceReferencesResponse, SavedObjectsUpdateObjectsSpacesObject, SavedObjectsUpdateObjectsSpacesOptions, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; import type { SavedObjectsType, @@ -2044,6 +2046,517 @@ describe('SavedObjectsRepository', () => { }); }); + describe('#bulkDelete', () => { + const obj1: SavedObjectsBulkDeleteObject = { + type: 'config', + id: '6.0.0-alpha1', + }; + const obj2: SavedObjectsBulkDeleteObject = { + type: 'index-pattern', + id: 'logstash-*', + }; + + const namespace = 'foo-namespace'; + + const createNamespaceAwareGetId = (type: string, id: string) => + `${registry.isSingleNamespace(type) && namespace ? `${namespace}:` : ''}${type}:${id}`; + + const getMockEsBulkDeleteResponse = ( + objects: TypeIdTuple[], + options?: SavedObjectsBulkDeleteOptions + ) => + ({ + items: objects.map(({ type, id }) => ({ + // es response returns more fields than what we're interested in. + delete: { + _id: `${ + registry.isSingleNamespace(type) && options?.namespace ? `${options?.namespace}:` : '' + }${type}:${id}`, + ...mockVersionProps, + result: 'deleted', + }, + })), + } as estypes.BulkResponse); + + const repositoryBulkDeleteSuccess = async ( + objects: SavedObjectsBulkDeleteObject[] = [], + options?: SavedObjectsBulkDeleteOptions, + internalOptions: { + mockMGetResponseWithObject?: { initialNamespaces: string[]; type: string; id: string }; + } = {} + ) => { + const multiNamespaceObjects = objects.filter(({ type }) => { + return registry.isMultiNamespace(type); + }); + + const { mockMGetResponseWithObject } = internalOptions; + if (multiNamespaceObjects.length > 0) { + const mockedMGetResponse = mockMGetResponseWithObject + ? getMockMgetResponse([mockMGetResponseWithObject], options?.namespace) + : getMockMgetResponse(multiNamespaceObjects, options?.namespace); + client.mget.mockResponseOnce(mockedMGetResponse); + } + const mockedEsBulkDeleteResponse = getMockEsBulkDeleteResponse(objects, options); + + client.bulk.mockResponseOnce(mockedEsBulkDeleteResponse); + const result = await savedObjectsRepository.bulkDelete(objects, options); + + expect(client.mget).toHaveBeenCalledTimes(multiNamespaceObjects?.length ? 1 : 0); + return result; + }; + + // bulk delete calls only has one object for each source -- the action + const expectClientCallBulkDeleteArgsAction = ( + objects: TypeIdTuple[], + { + method, + _index = expect.any(String), + getId = () => expect.any(String), + overrides = {}, + }: { + method: string; + _index?: string; + getId?: (type: string, id: string) => string; + overrides?: Record; + } + ) => { + const body = []; + for (const { type, id } of objects) { + body.push({ + [method]: { + _index, + _id: getId(type, id), + ...overrides, + }, + }); + } + + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ body }), + expect.anything() + ); + }; + + const createBulkDeleteFailStatus = ({ + type, + id, + error, + }: { + type: string; + id: string; + error?: ExpectedErrorResult['error']; + }) => ({ + type, + id, + success: false, + error: error ?? createBadRequestError(), + }); + + const createBulkDeleteSuccessStatus = ({ type, id }: { type: string; id: string }) => ({ + type, + id, + success: true, + }); + + // mocks a combination of success, error results for hidden and unknown object object types. + const repositoryBulkDeleteError = async ( + obj: SavedObjectsBulkDeleteObject, + isBulkError: boolean, + expectedErrorResult: ExpectedErrorResult + ) => { + const objects = [obj1, obj, obj2]; + const mockedBulkDeleteResponse = getMockEsBulkDeleteResponse(objects); + if (isBulkError) { + mockGetBulkOperationError.mockReturnValueOnce(undefined); + mockGetBulkOperationError.mockReturnValueOnce(expectedErrorResult.error as Payload); + } + client.bulk.mockResponseOnce(mockedBulkDeleteResponse); + + const result = await savedObjectsRepository.bulkDelete(objects); + expect(client.bulk).toHaveBeenCalled(); + expect(result).toEqual({ + statuses: [ + createBulkDeleteSuccessStatus(obj1), + createBulkDeleteFailStatus({ ...obj, error: expectedErrorResult.error }), + createBulkDeleteSuccessStatus(obj2), + ], + }); + }; + + const expectClientCallArgsAction = ( + objects: TypeIdTuple[], + { + method, + _index = expect.any(String), + getId = () => expect.any(String), + overrides = {}, + }: { + method: string; + _index?: string; + getId?: (type: string, id: string) => string; + overrides?: Record; + } + ) => { + const body = []; + for (const { type, id } of objects) { + body.push({ + [method]: { + _index, + _id: getId(type, id), + ...overrides, + }, + }); + } + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ body }), + expect.anything() + ); + }; + + const bulkDeleteMultiNamespaceError = async ( + [obj1, _obj, obj2]: SavedObjectsBulkDeleteObject[], + options: SavedObjectsBulkDeleteOptions | undefined, + mgetResponse: estypes.MgetResponse, + mgetOptions?: { statusCode?: number } + ) => { + const getId = (type: string, id: string) => `${options?.namespace}:${type}:${id}`; + // mock the response for the not found doc + client.mget.mockResponseOnce(mgetResponse, { statusCode: mgetOptions?.statusCode }); + // get a mocked response for the valid docs + const bulkResponse = getMockEsBulkDeleteResponse([obj1, obj2], { namespace }); + client.bulk.mockResponseOnce(bulkResponse); + + const result = await savedObjectsRepository.bulkDelete([obj1, _obj, obj2], options); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(client.mget).toHaveBeenCalledTimes(1); + + expectClientCallArgsAction([obj1, obj2], { method: 'delete', getId }); + expect(result).toEqual({ + statuses: [ + createBulkDeleteSuccessStatus(obj1), + { ...expectErrorNotFound(_obj), success: false }, + createBulkDeleteSuccessStatus(obj2), + ], + }); + }; + + beforeEach(() => { + mockDeleteLegacyUrlAliases.mockClear(); + mockDeleteLegacyUrlAliases.mockResolvedValue(); + }); + + describe('client calls', () => { + it(`should use the ES bulk action by default`, async () => { + await repositoryBulkDeleteSuccess([obj1, obj2]); + expect(client.bulk).toHaveBeenCalled(); + }); + + it(`should use the ES mget action before bulk action for any types that are multi-namespace`, async () => { + const objects = [obj1, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }]; + await repositoryBulkDeleteSuccess(objects); + expect(client.bulk).toHaveBeenCalled(); + expect(client.mget).toHaveBeenCalled(); + + const docs = [ + expect.objectContaining({ _id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${obj2.id}` }), + ]; + expect(client.mget).toHaveBeenCalledWith( + expect.objectContaining({ body: { docs } }), + expect.anything() + ); + }); + + it(`should not use the ES bulk action when there are no valid documents to delete`, async () => { + const objects = [obj1, obj2].map((x) => ({ ...x, type: 'unknownType' })); + await savedObjectsRepository.bulkDelete(objects); + expect(client.bulk).toHaveBeenCalledTimes(0); + }); + + it(`formats the ES request`, async () => { + const getId = createNamespaceAwareGetId; + await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); + expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + }); + + it(`formats the ES request for any types that are multi-namespace`, async () => { + const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; + const getId = createNamespaceAwareGetId; + await repositoryBulkDeleteSuccess([obj1, _obj2], { namespace }); + expectClientCallBulkDeleteArgsAction([obj1, _obj2], { method: 'delete', getId }); + }); + + it(`defaults to a refresh setting of wait_for`, async () => { + await repositoryBulkDeleteSuccess([obj1, obj2]); + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ refresh: 'wait_for' }), + expect.anything() + ); + }); + + it(`does not include the version of the existing document when not using a multi-namespace type`, async () => { + const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; + await repositoryBulkDeleteSuccess(objects); + expectClientCallBulkDeleteArgsAction(objects, { method: 'delete' }); + }); + + it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { + const getId = createNamespaceAwareGetId; + await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); + expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + }); + + it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { + const getId = (type: string, id: string) => `${type}:${id}`; + await repositoryBulkDeleteSuccess([obj1, obj2]); + expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + }); + + it(`normalizes options.namespace from 'default' to undefined`, async () => { + const getId = (type: string, id: string) => `${type}:${id}`; + await repositoryBulkDeleteSuccess([obj1, obj2], { namespace: 'default' }); + expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + }); + + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { + const getId = (type: string, id: string) => `${type}:${id}`; // not expecting namespace prefix; + const _obj1 = { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }; + const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; + + await repositoryBulkDeleteSuccess([_obj1, _obj2], { namespace }); + expectClientCallBulkDeleteArgsAction([_obj1, _obj2], { method: 'delete', getId }); + }); + }); + + describe('legacy URL aliases', () => { + it(`doesn't delete legacy URL aliases for single-namespace object types`, async () => { + await repositoryBulkDeleteSuccess([obj1, obj2]); + expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); + }); + + it(`deletes legacy URL aliases for multi-namespace object types (all spaces)`, async () => { + const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + const internalOptions = { + mockMGetResponseWithObject: { + ...testObject, + initialNamespaces: [ALL_NAMESPACES_STRING], + }, + }; + await repositoryBulkDeleteSuccess( + [testObject], + { namespace, force: true }, + internalOptions + ); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: MULTI_NAMESPACE_TYPE, + id: testObject.id, + namespaces: [], + deleteBehavior: 'exclusive', + }) + ); + }); + + it(`deletes legacy URL aliases for multi-namespace object types (specific space)`, async () => { + const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + const internalOptions = { + mockMGetResponseWithObject: { + ...testObject, + initialNamespaces: [namespace], + }, + }; + // specifically test against the current namespace + await repositoryBulkDeleteSuccess([testObject], { namespace }, internalOptions); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: MULTI_NAMESPACE_TYPE, + id: testObject.id, + namespaces: [namespace], + deleteBehavior: 'inclusive', + }) + ); + }); + + it(`deletes legacy URL aliases for multi-namespace object types shared to many specific spaces`, async () => { + const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + const initialTestObjectNamespaces = [namespace, 'bar-namespace']; + const internalOptions = { + mockMGetResponseWithObject: { + ...testObject, + initialNamespaces: initialTestObjectNamespaces, + }, + }; + // specifically test against named spaces ('*' is handled specifically, this assures we also take care of named spaces) + await repositoryBulkDeleteSuccess( + [testObject], + { namespace, force: true }, + internalOptions + ); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: MULTI_NAMESPACE_TYPE, + id: testObject.id, + namespaces: initialTestObjectNamespaces, + deleteBehavior: 'inclusive', + }) + ); + }); + + it(`logs a message when deleteLegacyUrlAliases returns an error`, async () => { + const testObject = { type: MULTI_NAMESPACE_ISOLATED_TYPE, id: obj1.id }; + + client.mget.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + getMockMgetResponse([testObject], namespace) + ) + ); + const mockedBulkResponse = getMockEsBulkDeleteResponse([testObject], { namespace }); + client.bulk.mockResolvedValueOnce(mockedBulkResponse); + + mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); + + await savedObjectsRepository.bulkDelete([testObject], { namespace }); + + expect(client.mget).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith( + 'Unable to delete aliases when deleting an object: Oh no!' + ); + }); + }); + + describe('errors', () => { + it(`throws an error when options.namespace is '*'`, async () => { + await expect( + savedObjectsRepository.bulkDelete([obj1], { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + }); + + it(`throws an error when client bulk response is not defined`, async () => { + client.mget.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + getMockMgetResponse([obj1], namespace) + ) + ); + const mockedBulkResponse = undefined; + // we have to cast here to test the assumption we always get a response. + client.bulk.mockResponseOnce(mockedBulkResponse as unknown as estypes.BulkResponse); + await expect(savedObjectsRepository.bulkDelete([obj1], { namespace })).rejects.toThrowError( + 'Unexpected error in bulkDelete saved objects: bulkDeleteResponse is undefined' + ); + }); + + it(`returns an error for the object when the object's type is invalid`, async () => { + const unknownObjType = { ...obj1, type: 'unknownType' }; + await repositoryBulkDeleteError( + unknownObjType, + false, + expectErrorInvalidType(unknownObjType) + ); + }); + + it(`returns an error for an object when the object's type is hidden`, async () => { + const hiddenObject = { ...obj1, type: HIDDEN_TYPE }; + await repositoryBulkDeleteError(hiddenObject, false, expectErrorInvalidType(hiddenObject)); + }); + + it(`returns an error when ES is unable to find the document during mget`, async () => { + const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + const mgetResponse = getMockMgetResponse([notFoundObj], namespace); + await bulkDeleteMultiNamespaceError([obj1, notFoundObj, obj2], { namespace }, mgetResponse); + }); + + it(`returns an error when ES is unable to find the index during mget`, async () => { + const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + await bulkDeleteMultiNamespaceError( + [obj1, notFoundObj, obj2], + { namespace }, + {} as estypes.MgetResponse, + { + statusCode: 404, + } + ); + }); + + it(`returns an error when the type is multi-namespace and the document exists, but not in this namespace`, async () => { + const obj = { + type: MULTI_NAMESPACE_ISOLATED_TYPE, + id: 'three', + namespace: 'bar-namespace', + }; + const mgetResponse = getMockMgetResponse([obj], namespace); + await bulkDeleteMultiNamespaceError([obj1, obj, obj2], { namespace }, mgetResponse); + }); + + it(`returns an error when the type is multi-namespace and the document has multiple namespaces and the force option is not enabled`, async () => { + const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + const internalOptions = { + mockMGetResponseWithObject: { + ...testObject, + initialNamespaces: [namespace, 'bar-namespace'], + }, + }; + const result = await repositoryBulkDeleteSuccess( + [testObject], + { namespace }, + internalOptions + ); + expect(result.statuses[0]).toStrictEqual( + createBulkDeleteFailStatus({ + ...testObject, + error: createBadRequestError( + 'Unable to delete saved object that exists in multiple namespaces, use the "force" option to delete it anyway' + ), + }) + ); + }); + + it(`returns an error when the type is multi-namespace and the document has all namespaces and the force option is not enabled`, async () => { + const testObject = { ...obj1, type: ALL_NAMESPACES_STRING }; + const internalOptions = { + mockMGetResponseWithObject: { + ...testObject, + initialNamespaces: [namespace, 'bar-namespace'], + }, + }; + const result = await repositoryBulkDeleteSuccess( + [testObject], + { namespace }, + internalOptions + ); + expect(result.statuses[0]).toStrictEqual( + createBulkDeleteFailStatus({ + ...testObject, + error: createBadRequestError("Unsupported saved object type: '*'"), + }) + ); + }); + }); + + describe('returns', () => { + it(`returns early for empty objects argument`, async () => { + await savedObjectsRepository.bulkDelete([], { namespace }); + expect(client.bulk).toHaveBeenCalledTimes(0); + }); + + it(`formats the ES response`, async () => { + const response = await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); + expect(response).toEqual({ + statuses: [obj1, obj2].map(createBulkDeleteSuccessStatus), + }); + }); + + it(`handles a mix of successful deletes and errors`, async () => { + const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + await bulkDeleteMultiNamespaceError( + [obj1, notFoundObj, obj2], + { namespace }, + {} as estypes.MgetResponse, + { statusCode: 404 } + ); + }); + }); + }); + describe('#checkConflicts', () => { const obj1 = { type: 'dashboard', id: 'one' }; const obj2 = { type: 'dashboard', id: 'two' }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 961b44a1cd688..5569141c7fa0e 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -54,6 +54,9 @@ import type { SavedObjectsClosePointInTimeOptions, SavedObjectsCreatePointInTimeFinderOptions, SavedObjectsFindOptions, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteResponse, } from '@kbn/core-saved-objects-api-server'; import type { SavedObjectSanitizedDoc, @@ -83,6 +86,7 @@ import { type IndexMapping, type IKibanaMigrator, } from '@kbn/core-saved-objects-base-server-internal'; +import pMap from 'p-map'; import { PointInTimeFinder } from './point_in_time_finder'; import { createRepositoryEsClient, RepositoryEsClient } from './repository_es_client'; import { getSearchDsl } from './search_dsl'; @@ -109,6 +113,16 @@ import { PreflightCheckForCreateObject, } from './preflight_check_for_create'; import { deleteLegacyUrlAliases } from './legacy_url_aliases'; +import type { + BulkDeleteParams, + ExpectedBulkDeleteResult, + BulkDeleteItemErrorResult, + NewBulkItemResponse, + BulkDeleteExpectedBulkGetResult, + PreflightCheckForBulkDeleteParams, + ExpectedBulkDeleteMultiNamespaceDocsParams, + ObjectToDeleteAliasesFor, +} from './repository_bulk_delete_internal_types'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -127,6 +141,7 @@ export interface SavedObjectsRepositoryOptions { export const DEFAULT_REFRESH_SETTING = 'wait_for'; export const DEFAULT_RETRY_COUNT = 3; +const MAX_CONCURRENT_ALIAS_DELETIONS = 10; /** * @internal */ @@ -676,7 +691,6 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const { refresh = DEFAULT_REFRESH_SETTING, force } = options; const namespace = normalizeNamespace(options.namespace); @@ -762,6 +776,286 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ); } + /** + * Performs initial checks on object type validity and flags multi-namespace objects for preflight checks by adding an `esRequestIndex` + * @param objects SavedObjectsBulkDeleteObject[] + * @returns array BulkDeleteExpectedBulkGetResult[] + * @internal + */ + private presortObjectsByNamespaceType(objects: SavedObjectsBulkDeleteObject[]) { + let bulkGetRequestIndexCounter = 0; + return objects.map((object) => { + const { type, id } = object; + if (!this._allowedTypes.includes(type)) { + return { + tag: 'Left', + value: { + id, + type, + error: errorContent(SavedObjectsErrorHelpers.createUnsupportedTypeError(type)), + }, + }; + } + const requiresNamespacesCheck = this._registry.isMultiNamespace(type); + return { + tag: 'Right', + value: { + type, + id, + ...(requiresNamespacesCheck && { esRequestIndex: bulkGetRequestIndexCounter++ }), + }, + }; + }); + } + + /** + * Fetch multi-namespace saved objects + * @returns MgetResponse + * @notes multi-namespace objects shared to more than one space require special handling. We fetch these docs to retrieve their namespaces. + * @internal + */ + private async preflightCheckForBulkDelete(params: PreflightCheckForBulkDeleteParams) { + const { expectedBulkGetResults, namespace } = params; + const bulkGetMultiNamespaceDocs = expectedBulkGetResults + .filter(isRight) + .filter(({ value }) => value.esRequestIndex !== undefined) + .map(({ value: { type, id } }) => ({ + _id: this._serializer.generateRawId(namespace, type, id), + _index: this.getIndexForType(type), + _source: ['type', 'namespaces'], + })); + + const bulkGetMultiNamespaceDocsResponse = bulkGetMultiNamespaceDocs.length + ? await this.client.mget( + { body: { docs: bulkGetMultiNamespaceDocs } }, + { ignore: [404], meta: true } + ) + : undefined; + // fail fast if we can't verify a 404 response is from Elasticsearch + if ( + bulkGetMultiNamespaceDocsResponse && + isNotFoundFromUnsupportedServer({ + statusCode: bulkGetMultiNamespaceDocsResponse.statusCode, + headers: bulkGetMultiNamespaceDocsResponse.headers, + }) + ) { + throw SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError(); + } + return bulkGetMultiNamespaceDocsResponse; + } + + /** + * @returns array of objects sorted by expected delete success or failure result + * @internal + */ + private getExpectedBulkDeleteMultiNamespaceDocsResults( + params: ExpectedBulkDeleteMultiNamespaceDocsParams + ): ExpectedBulkDeleteResult[] { + const { expectedBulkGetResults, multiNamespaceDocsResponse, namespace, force } = params; + let indexCounter = 0; + const expectedBulkDeleteMultiNamespaceDocsResults = + expectedBulkGetResults.map((expectedBulkGetResult) => { + if (isLeft(expectedBulkGetResult)) { + return { ...expectedBulkGetResult }; + } + const { esRequestIndex: esBulkGetRequestIndex, id, type } = expectedBulkGetResult.value; + + let namespaces; + + if (esBulkGetRequestIndex !== undefined) { + const indexFound = multiNamespaceDocsResponse?.statusCode !== 404; + + const actualResult = indexFound + ? multiNamespaceDocsResponse?.body.docs[esBulkGetRequestIndex] + : undefined; + + const docFound = indexFound && isMgetDoc(actualResult) && actualResult.found; + + // return an error if the doc isn't found at all or the doc doesn't exist in the namespaces + if (!docFound) { + return { + tag: 'Left', + value: { + id, + type, + error: errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)), + }, + }; + } + // the following check should be redundant since we're retrieving the docs from elasticsearch but we check just to make sure + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + if (!this.rawDocExistsInNamespace(actualResult, namespace)) { + return { + tag: 'Left', + value: { + id, + type, + error: errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)), + }, + }; + } + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + namespaces = actualResult!._source.namespaces ?? [ + SavedObjectsUtils.namespaceIdToString(namespace), + ]; + const useForce = force && force === true ? true : false; + // the document is shared to more than one space and can only be deleted by force. + if (!useForce && (namespaces.length > 1 || namespaces.includes(ALL_NAMESPACES_STRING))) { + return { + tag: 'Left', + value: { + success: false, + id, + type, + error: errorContent( + SavedObjectsErrorHelpers.createBadRequestError( + `Unable to delete saved object that exists in multiple namespaces, use the "force" option to delete it anyway` + ) + ), + }, + }; + } + } + // contains all objects that passed initial preflight checks, including single namespace objects that skipped the mget call + // single namespace objects will have namespaces:undefined + const expectedResult = { + type, + id, + namespaces, + esRequestIndex: indexCounter++, + }; + + return { tag: 'Right', value: expectedResult }; + }); + return expectedBulkDeleteMultiNamespaceDocsResults; + } + + /** + * {@inheritDoc ISavedObjectsRepository.bulkDelete} + */ + async bulkDelete( + objects: SavedObjectsBulkDeleteObject[], + options: SavedObjectsBulkDeleteOptions = {} + ): Promise { + const { refresh = DEFAULT_REFRESH_SETTING, force } = options; + const namespace = normalizeNamespace(options.namespace); + const expectedBulkGetResults = this.presortObjectsByNamespaceType(objects); + const multiNamespaceDocsResponse = await this.preflightCheckForBulkDelete({ + expectedBulkGetResults, + namespace, + }); + const bulkDeleteParams: BulkDeleteParams[] = []; + + const expectedBulkDeleteMultiNamespaceDocsResults = + this.getExpectedBulkDeleteMultiNamespaceDocsResults({ + expectedBulkGetResults, + multiNamespaceDocsResponse, + namespace, + force, + }); + // bulk up the bulkDeleteParams + expectedBulkDeleteMultiNamespaceDocsResults.map((expectedResult) => { + if (isRight(expectedResult)) { + bulkDeleteParams.push({ + delete: { + _id: this._serializer.generateRawId( + namespace, + expectedResult.value.type, + expectedResult.value.id + ), + _index: this.getIndexForType(expectedResult.value.type), + ...getExpectedVersionProperties(undefined), + }, + }); + } + }); + + const bulkDeleteResponse = bulkDeleteParams.length + ? await this.client.bulk({ + refresh, + body: bulkDeleteParams, + require_alias: true, + }) + : undefined; + + // extracted to ensure consistency in the error results returned + let errorResult: BulkDeleteItemErrorResult; + const objectsToDeleteAliasesFor: ObjectToDeleteAliasesFor[] = []; + + const savedObjects = expectedBulkDeleteMultiNamespaceDocsResults.map((expectedResult) => { + if (isLeft(expectedResult)) { + return { ...expectedResult.value, success: false }; + } + const { + type, + id, + namespaces, + esRequestIndex: esBulkDeleteRequestIndex, + } = expectedResult.value; + // we assume this wouldn't happen but is needed to ensure type consistency + if (bulkDeleteResponse === undefined) { + throw new Error( + `Unexpected error in bulkDelete saved objects: bulkDeleteResponse is undefined` + ); + } + const rawResponse = Object.values( + bulkDeleteResponse.items[esBulkDeleteRequestIndex] + )[0] as NewBulkItemResponse; + + const error = getBulkOperationError(type, id, rawResponse); + if (error) { + errorResult = { success: false, type, id, error }; + return errorResult; + } + if (rawResponse.result === 'not_found') { + errorResult = { + success: false, + type, + id, + error: errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)), + }; + return errorResult; + } + + if (rawResponse.result === 'deleted') { + // `namespaces` should only exist in the expectedResult.value if the type is multi-namespace. + if (namespaces) { + objectsToDeleteAliasesFor.push({ + type, + id, + ...(namespaces.includes(ALL_NAMESPACES_STRING) + ? { namespaces: [], deleteBehavior: 'exclusive' } + : { namespaces, deleteBehavior: 'inclusive' }), + }); + } + } + const successfulResult = { + success: true, + id, + type, + }; + return successfulResult; + }); + + // Delete aliases if necessary, ensuring we don't have too many concurrent operations running. + const mapper = async ({ type, id, namespaces, deleteBehavior }: ObjectToDeleteAliasesFor) => + await deleteLegacyUrlAliases({ + mappings: this._mappings, + registry: this._registry, + client: this.client, + getIndexForType: this.getIndexForType.bind(this), + type, + id, + namespaces, + deleteBehavior, + }).catch((err) => { + this._logger.error(`Unable to delete aliases when deleting an object: ${err.message}`); + }); + await pMap(objectsToDeleteAliasesFor, mapper, { concurrency: MAX_CONCURRENT_ALIAS_DELETIONS }); + + return { statuses: [...savedObjects] }; + } + /** * {@inheritDoc ISavedObjectsRepository.deleteByNamespace} */ diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts new file mode 100644 index 0000000000000..93d4354d8d7e8 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import type { Payload } from '@hapi/boom'; +import { + BulkOperationBase, + BulkResponseItem, + ErrorCause, +} from '@elastic/elasticsearch/lib/api/types'; +import type { estypes, TransportResult } from '@elastic/elasticsearch'; +import { Either } from './internal_utils'; +import { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; + +/** + * @internal + */ +export interface PreflightCheckForBulkDeleteParams { + expectedBulkGetResults: BulkDeleteExpectedBulkGetResult[]; + namespace?: string; +} + +/** + * @internal + */ +export interface ExpectedBulkDeleteMultiNamespaceDocsParams { + // contains the type and id of all objects to delete + expectedBulkGetResults: BulkDeleteExpectedBulkGetResult[]; + // subset of multi-namespace only expectedBulkGetResults + multiNamespaceDocsResponse: TransportResult, unknown> | undefined; + // current namespace in which the bulkDelete call is made + namespace: string | undefined; + // optional parameter used to force delete multinamespace objects that exist in more than the current space + force?: boolean; +} +/** + * @internal + */ +export interface BulkDeleteParams { + delete: Omit; +} + +/** + * @internal + */ +export type ExpectedBulkDeleteResult = Either< + { type: string; id: string; error: Payload }, + { + type: string; + id: string; + namespaces: string[]; + esRequestIndex: number; + } +>; + +/** + * @internal + */ +export interface BulkDeleteItemErrorResult { + success: boolean; + type: string; + id: string; + error: Payload; +} + +/** + * @internal + */ +export type NewBulkItemResponse = BulkResponseItem & { error: ErrorCause & { index: string } }; + +/** + * @internal + * @note Contains all documents for bulk delete, regardless of namespace type + */ +export type BulkDeleteExpectedBulkGetResult = Either< + { type: string; id: string; error: Payload }, + { type: string; id: string; version?: string; esRequestIndex?: number } +>; + +export type ObjectToDeleteAliasesFor = Pick< + DeleteLegacyUrlAliasesParams, + 'type' | 'id' | 'namespaces' | 'deleteBehavior' +>; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts index 2cdfcf1710ad4..dc6c06c0c828d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts @@ -16,6 +16,7 @@ const createRepositoryMock = () => { create: jest.fn(), bulkCreate: jest.fn(), bulkUpdate: jest.fn(), + bulkDelete: jest.fn(), delete: jest.fn(), bulkGet: jest.fn(), find: jest.fn(), diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts index 5829f34a6ba79..38d4e75a0c528 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts @@ -23,6 +23,8 @@ import type { SavedObjectsFindOptions, SavedObjectsUpdateObjectsSpacesObject, SavedObjectsUpdateObjectsSpacesOptions, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteObject, } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsClient } from './saved_objects_client'; import { repositoryMock, savedObjectsPointInTimeFinderMock } from './mocks'; @@ -119,6 +121,22 @@ describe('SavedObjectsClient', () => { }); }); + test(`#bulkDelete`, async () => { + const returnValue: any = Symbol(); + mockRepository.bulkDelete.mockResolvedValueOnce(returnValue); + const client = new SavedObjectsClient(mockRepository); + + const objects: SavedObjectsBulkDeleteObject[] = [ + { type: 'foo', id: '1' }, + { type: 'bar', id: '2' }, + ]; + const options: SavedObjectsBulkDeleteOptions = { namespace: 'ns-1', refresh: true }; + const result = await client.bulkDelete(objects, options); + + expect(mockRepository.bulkDelete).toHaveBeenCalledWith(objects, options); + expect(result).toBe(returnValue); + }); + test(`#delete`, async () => { const returnValue: any = Symbol(); mockRepository.delete.mockResolvedValueOnce(returnValue); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts index 7c2b3a205b76d..50f78f09dd684 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts @@ -39,6 +39,9 @@ import type { SavedObjectsClosePointInTimeOptions, SavedObjectsCreatePointInTimeFinderOptions, SavedObjectsFindOptions, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteResponse, } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; @@ -83,6 +86,14 @@ export class SavedObjectsClient implements SavedObjectsClientContract { return await this._repository.delete(type, id, options); } + /** {@inheritDoc SavedObjectsClientContract.bulkDelete} */ + async bulkDelete( + objects: SavedObjectsBulkDeleteObject[], + options: SavedObjectsBulkDeleteOptions = {} + ): Promise { + return await this._repository.bulkDelete(objects, options); + } + /** {@inheritDoc SavedObjectsClientContract.find} */ async find( options: SavedObjectsFindOptions diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts index d950b041d2432..168f4c8de6b59 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts @@ -15,6 +15,7 @@ const create = () => { create: jest.fn(), bulkCreate: jest.fn(), bulkUpdate: jest.fn(), + bulkDelete: jest.fn(), delete: jest.fn(), bulkGet: jest.fn(), find: jest.fn(), diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts index 75ee540cb7d8a..523e5003e650f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts @@ -18,6 +18,7 @@ const create = () => { checkConflicts: jest.fn(), bulkUpdate: jest.fn(), delete: jest.fn(), + bulkDelete: jest.fn(), bulkGet: jest.fn(), find: jest.fn(), get: jest.fn(), diff --git a/packages/core/saved-objects/core-saved-objects-api-server/index.ts b/packages/core/saved-objects/core-saved-objects-api-server/index.ts index 1c9688a236920..fdaa5685fbde0 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/index.ts @@ -52,4 +52,8 @@ export type { SavedObjectsCreatePointInTimeFinderOptions, SavedObjectsFindOptions, SavedObjectsPointInTimeFinderClient, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteStatus, + SavedObjectsBulkDeleteResponse, } from './src/apis'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts new file mode 100644 index 0000000000000..76d490925c580 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.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 type { SavedObjectError } from '@kbn/core-saved-objects-common'; +import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; + +/** + * + * @public + */ +export interface SavedObjectsBulkDeleteObject { + type: string; + id: string; +} + +/** + * @public + */ +export interface SavedObjectsBulkDeleteOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; + /** + * Force deletion of all objects that exists in multiple namespaces, applied to all objects. + */ + force?: boolean; +} + +/** + * @public + */ +export interface SavedObjectsBulkDeleteStatus { + id: string; + type: string; + /** The status of deleting the object: true for deleted, false for error */ + success: boolean; + /** Reason the object could not be deleted (success is false) */ + error?: SavedObjectError; +} + +/** + * @public + */ +export interface SavedObjectsBulkDeleteResponse { + statuses: SavedObjectsBulkDeleteStatus[]; +} diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/index.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/index.ts index 7dc8e7ab09fc6..d311f2316885d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/index.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/index.ts @@ -72,3 +72,9 @@ export type { SavedObjectsUpdateObjectsSpacesOptions, SavedObjectsUpdateObjectsSpacesResponseObject, } from './update_objects_spaces'; +export type { + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteStatus, + SavedObjectsBulkDeleteResponse, +} from './bulk_delete'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts index 82808e4024c73..cfe1f4e6a146b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts @@ -41,6 +41,9 @@ import type { SavedObjectsRemoveReferencesToResponse, SavedObjectsCollectMultiNamespaceReferencesOptions, SavedObjectsBulkResponse, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteResponse, } from './apis'; /** @@ -151,6 +154,16 @@ export interface SavedObjectsClientContract { */ delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + /** + * Deletes multiple SavedObjects batched together as a single request + * + * @param objects + * @param options + */ + bulkDelete( + objects: SavedObjectsBulkDeleteObject[], + options?: SavedObjectsBulkDeleteOptions + ): Promise; /** * Find all SavedObjects matching the search query * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index 102afce9dd73d..d7d2ca57ae3a2 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -44,6 +44,9 @@ import type { SavedObjectsDeleteByNamespaceOptions, SavedObjectsIncrementCounterField, SavedObjectsIncrementCounterOptions, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteResponse, } from './apis'; /** @@ -105,6 +108,17 @@ export interface ISavedObjectsRepository { */ delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + /** + * Deletes multiple documents at once + * @param {array} objects - an array of objects containing id and type + * @param {object} [options={}] + * @returns {promise} - { statuses: [{ id, type, success, error: { message } }] } + */ + bulkDelete( + objects: SavedObjectsBulkDeleteObject[], + options?: SavedObjectsBulkDeleteOptions + ): Promise; + /** * Deletes all objects from the provided namespace. * diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts index 77391343cd033..6c2966ee9775f 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts @@ -308,6 +308,45 @@ describe('SavedObjectsClient', () => { }); }); + describe('#bulk_delete', () => { + const bulkDeleteDoc = { + id: 'AVwSwFxtcMV38qjDZoQg', + type: 'config', + }; + beforeEach(() => { + http.fetch.mockResolvedValue({ + statuses: [{ id: bulkDeleteDoc.id, type: bulkDeleteDoc.type, success: true }], + }); + }); + + test('deletes with an array of id, type and success status for deleted docs', async () => { + const response = savedObjectsClient.bulkDelete([bulkDeleteDoc]); + await expect(response).resolves.toHaveProperty('statuses'); + + const result = await response; + expect(result.statuses).toHaveLength(1); + expect(result.statuses[0]).toHaveProperty('success'); + }); + + test('makes HTTP call', async () => { + await savedObjectsClient.bulkDelete([bulkDeleteDoc]); + expect(http.fetch.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/api/saved_objects/_bulk_delete", + Object { + "body": "[{\\"type\\":\\"config\\",\\"id\\":\\"AVwSwFxtcMV38qjDZoQg\\"}]", + "method": "POST", + "query": Object { + "force": false, + }, + }, + ], + ] + `); + }); + }); + describe('#update', () => { const attributes = { foo: 'Foo', bar: 'Bar' }; const options = { version: '1' }; diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts index 3a16030983fa9..dd2feed58123f 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts @@ -11,9 +11,11 @@ import type { HttpSetup, HttpFetchOptions } from '@kbn/core-http-browser'; import type { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; import type { SavedObjectsBulkResolveResponse as SavedObjectsBulkResolveResponseServer, + SavedObjectsBulkDeleteResponse as SavedObjectsBulkDeleteResponseServer, SavedObjectsClientContract as SavedObjectsApi, SavedObjectsFindResponse as SavedObjectsFindResponseServer, SavedObjectsResolveResponse, + SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; import type { SavedObjectsClientContract, @@ -28,6 +30,7 @@ import type { SavedObjectsBulkCreateOptions, SavedObjectsBulkCreateObject, SimpleSavedObject, + SavedObjectsBulkDeleteResponse, } from '@kbn/core-saved-objects-api-browser'; import { SimpleSavedObjectImpl } from './simple_saved_object'; @@ -255,6 +258,31 @@ export class SavedObjectsClient implements SavedObjectsClientContract { return this.savedObjectsFetch(this.getPath([type, id]), { method: 'DELETE', query }); }; + public bulkDelete = async ( + objects: SavedObjectTypeIdTuple[], + options?: SavedObjectsBulkDeleteOptions + ): Promise => { + const filteredObjects = objects.map(({ type, id }) => ({ type, id })); + const queryOptions = { force: !!options?.force }; + const response = await this.performBulkDelete(filteredObjects, queryOptions); + return { + statuses: response.statuses, + }; + }; + + private async performBulkDelete( + objects: SavedObjectTypeIdTuple[], + queryOptions: { force: boolean } + ) { + const path = this.getPath(['_bulk_delete']); + const request: Promise = this.savedObjectsFetch(path, { + method: 'POST', + body: JSON.stringify(objects), + query: queryOptions, + }); + return request; + } + public find = ( options: SavedObjectsFindOptions ): Promise> => { diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts index 0caa572238807..2239b94d7e2eb 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts @@ -19,6 +19,7 @@ const createStartContractMock = () => { bulkCreate: jest.fn(), bulkResolve: jest.fn(), bulkUpdate: jest.fn(), + bulkDelete: jest.fn(), delete: jest.fn(), bulkGet: jest.fn(), find: jest.fn(), diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/index.ts b/packages/core/saved-objects/core-saved-objects-server-internal/index.ts index caeb029e037f7..f7d6fa7918031 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/index.ts @@ -22,6 +22,7 @@ export { registerBulkCreateRoute } from './src/routes/bulk_create'; export { registerBulkGetRoute } from './src/routes/bulk_get'; export { registerBulkResolveRoute } from './src/routes/bulk_resolve'; export { registerBulkUpdateRoute } from './src/routes/bulk_update'; +export { registerBulkDeleteRoute } from './src/routes/bulk_delete'; export { registerCreateRoute } from './src/routes/create'; export { registerDeleteRoute } from './src/routes/delete'; export { registerExportRoute } from './src/routes/export'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts new file mode 100644 index 0000000000000..f435eadebd066 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.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 { schema } from '@kbn/config-schema'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; +import { catchAndReturnBoomErrors } from './utils'; + +interface RouteDependencies { + coreUsageData: InternalCoreUsageDataSetup; +} + +export const registerBulkDeleteRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { + router.post( + { + path: '/_bulk_delete', + validate: { + body: schema.arrayOf( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ), + query: schema.object({ + force: schema.maybe(schema.boolean()), + }), + }, + }, + catchAndReturnBoomErrors(async (context, req, res) => { + const { force } = req.query; + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsBulkDelete({ request: req }).catch(() => {}); + + const { savedObjects } = await context.core; + + const statuses = await savedObjects.client.bulkDelete(req.body, { force }); + return res.ok({ body: statuses }); + }) + ); +}; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/index.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/index.ts index 528793e8539bd..89d5b41dd8885 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/index.ts @@ -23,6 +23,7 @@ import { registerUpdateRoute } from './update'; import { registerBulkGetRoute } from './bulk_get'; import { registerBulkCreateRoute } from './bulk_create'; import { registerBulkUpdateRoute } from './bulk_update'; +import { registerBulkDeleteRoute } from './bulk_delete'; import { registerExportRoute } from './export'; import { registerImportRoute } from './import'; import { registerResolveImportErrorsRoute } from './resolve_import_errors'; @@ -62,6 +63,7 @@ export function registerRoutes({ registerBulkCreateRoute(router, { coreUsageData }); registerBulkResolveRoute(router, { coreUsageData }); registerBulkUpdateRoute(router, { coreUsageData }); + registerBulkDeleteRoute(router, { coreUsageData }); registerExportRoute(router, { config, coreUsageData }); registerImportRoute(router, { config, coreUsageData }); registerResolveImportErrorsRoute(router, { config, coreUsageData }); diff --git a/packages/core/usage-data/core-usage-data-base-server-internal/src/usage_stats_client.ts b/packages/core/usage-data/core-usage-data-base-server-internal/src/usage_stats_client.ts index 3a603ebfdf5f0..735a4ab261658 100644 --- a/packages/core/usage-data/core-usage-data-base-server-internal/src/usage_stats_client.ts +++ b/packages/core/usage-data/core-usage-data-base-server-internal/src/usage_stats_client.ts @@ -43,6 +43,8 @@ export interface ICoreUsageStatsClient { incrementSavedObjectsBulkUpdate(options: BaseIncrementOptions): Promise; + incrementSavedObjectsBulkDelete(options: BaseIncrementOptions): Promise; + incrementSavedObjectsCreate(options: BaseIncrementOptions): Promise; incrementSavedObjectsDelete(options: BaseIncrementOptions): Promise; diff --git a/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_stats_client.test.ts b/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_stats_client.test.ts index f00341fdad0a7..1b7d332743697 100644 --- a/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_stats_client.test.ts +++ b/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_stats_client.test.ts @@ -20,6 +20,7 @@ import { BULK_CREATE_STATS_PREFIX, BULK_GET_STATS_PREFIX, BULK_UPDATE_STATS_PREFIX, + BULK_DELETE_STATS_PREFIX, CREATE_STATS_PREFIX, DELETE_STATS_PREFIX, FIND_STATS_PREFIX, @@ -452,6 +453,81 @@ describe('CoreUsageStatsClient', () => { }); }); + describe('#incrementSavedObjectsBulkDelete', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsBulkDelete({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_DELETE_STATS_PREFIX}.total`, + `${BULK_DELETE_STATS_PREFIX}.namespace.default.total`, + `${BULK_DELETE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsBulkDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_DELETE_STATS_PREFIX}.total`, + `${BULK_DELETE_STATS_PREFIX}.namespace.default.total`, + `${BULK_DELETE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_DELETE_STATS_PREFIX}.total`, + `${BULK_DELETE_STATS_PREFIX}.namespace.custom.total`, + `${BULK_DELETE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + describe('#incrementSavedObjectsDelete', () => { it('does not throw an error if repository incrementCounter operation fails', async () => { const { usageStatsClient, repositoryMock } = setup(); diff --git a/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_stats_client.ts b/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_stats_client.ts index 49c7333bea772..3bafa2e20e562 100644 --- a/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_stats_client.ts +++ b/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_stats_client.ts @@ -25,6 +25,7 @@ export const BULK_CREATE_STATS_PREFIX = 'apiCalls.savedObjectsBulkCreate'; export const BULK_GET_STATS_PREFIX = 'apiCalls.savedObjectsBulkGet'; export const BULK_RESOLVE_STATS_PREFIX = 'apiCalls.savedObjectsBulkResolve'; export const BULK_UPDATE_STATS_PREFIX = 'apiCalls.savedObjectsBulkUpdate'; +export const BULK_DELETE_STATS_PREFIX = 'apiCalls.savedObjectsBulkDelete'; export const CREATE_STATS_PREFIX = 'apiCalls.savedObjectsCreate'; export const DELETE_STATS_PREFIX = 'apiCalls.savedObjectsDelete'; export const FIND_STATS_PREFIX = 'apiCalls.savedObjectsFind'; @@ -43,6 +44,7 @@ const ALL_COUNTER_FIELDS = [ ...getFieldsForCounter(BULK_GET_STATS_PREFIX), ...getFieldsForCounter(BULK_RESOLVE_STATS_PREFIX), ...getFieldsForCounter(BULK_UPDATE_STATS_PREFIX), + ...getFieldsForCounter(BULK_DELETE_STATS_PREFIX), ...getFieldsForCounter(CREATE_STATS_PREFIX), ...getFieldsForCounter(DELETE_STATS_PREFIX), ...getFieldsForCounter(FIND_STATS_PREFIX), @@ -114,6 +116,10 @@ export class CoreUsageStatsClient implements ICoreUsageStatsClient { await this.updateUsageStats([], BULK_UPDATE_STATS_PREFIX, options); } + public async incrementSavedObjectsBulkDelete(options: BaseIncrementOptions) { + await this.updateUsageStats([], BULK_DELETE_STATS_PREFIX, options); + } + public async incrementSavedObjectsCreate(options: BaseIncrementOptions) { await this.updateUsageStats([], CREATE_STATS_PREFIX, options); } diff --git a/packages/core/usage-data/core-usage-data-server-mocks/src/core_usage_stats_client.mock.ts b/packages/core/usage-data/core-usage-data-server-mocks/src/core_usage_stats_client.mock.ts index a6e76e33057dc..6da6da69f4962 100644 --- a/packages/core/usage-data/core-usage-data-server-mocks/src/core_usage_stats_client.mock.ts +++ b/packages/core/usage-data/core-usage-data-server-mocks/src/core_usage_stats_client.mock.ts @@ -15,6 +15,7 @@ const createUsageStatsClientMock = () => incrementSavedObjectsBulkGet: jest.fn().mockResolvedValue(null), incrementSavedObjectsBulkResolve: jest.fn().mockResolvedValue(null), incrementSavedObjectsBulkUpdate: jest.fn().mockResolvedValue(null), + incrementSavedObjectsBulkDelete: jest.fn().mockResolvedValue(null), incrementSavedObjectsCreate: jest.fn().mockResolvedValue(null), incrementSavedObjectsDelete: jest.fn().mockResolvedValue(null), incrementSavedObjectsFind: jest.fn().mockResolvedValue(null), diff --git a/packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts b/packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts index aef5b657fb6f7..279d5c68cd733 100644 --- a/packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts +++ b/packages/core/usage-data/core-usage-data-server/src/core_usage_stats.ts @@ -42,6 +42,13 @@ export interface CoreUsageStats { 'apiCalls.savedObjectsBulkUpdate.namespace.custom.total'?: number; 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes'?: number; 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkDelete.total'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.default.total'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.custom.total'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsCreate.total'?: number; 'apiCalls.savedObjectsCreate.namespace.default.total'?: number; 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes'?: number; diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_breakdown_metrics.ts b/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_breakdown_metrics.ts index 1772b5f655713..26a908bd085ab 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_breakdown_metrics.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_breakdown_metrics.ts @@ -105,15 +105,16 @@ export function getBreakdownMetrics(events: ApmFields[]) { lastMeasurement = timestamp; } + const instance = pickBy(event, instancePicker); + const key = { '@timestamp': event['@timestamp']! - (event['@timestamp']! % (30 * 1000)), 'transaction.type': transaction['transaction.type'], 'transaction.name': transaction['transaction.name'], ...pickBy(event, metricsetPicker), + ...instance, }; - const instance = pickBy(event, instancePicker); - const metricsetId = objectHash(key); let metricset = metricsets.get(metricsetId); @@ -121,7 +122,6 @@ export function getBreakdownMetrics(events: ApmFields[]) { if (!metricset) { metricset = { ...key, - ...instance, 'processor.event': 'metric', 'processor.name': 'metric', 'metricset.name': `span_breakdown`, diff --git a/packages/kbn-dev-cli-runner/index.ts b/packages/kbn-dev-cli-runner/index.ts index aa56ab0e976ce..0bc2b64c64d7c 100644 --- a/packages/kbn-dev-cli-runner/index.ts +++ b/packages/kbn-dev-cli-runner/index.ts @@ -9,4 +9,5 @@ export * from './src/run'; export * from './src/run_with_commands'; export * from './src/flags'; +export * from './src/flags_reader'; export type { CleanupTask } from './src/cleanup'; diff --git a/packages/kbn-dev-cli-runner/src/flags.ts b/packages/kbn-dev-cli-runner/src/flags.ts index 919da586f7ba6..595205c3e0333 100644 --- a/packages/kbn-dev-cli-runner/src/flags.ts +++ b/packages/kbn-dev-cli-runner/src/flags.ts @@ -53,6 +53,10 @@ export function mergeFlagOptions(global: FlagOptions = {}, local: FlagOptions = }; } +export const DEFAULT_FLAG_ALIASES = { + v: 'verbose', +}; + export function getFlags( argv: string[], flagOptions: RunOptions['flags'] = {}, @@ -67,7 +71,7 @@ export function getFlags( boolean: [...(flagOptions.boolean || []), ...logLevelFlags, 'help'], alias: { ...flagOptions.alias, - v: 'verbose', + ...DEFAULT_FLAG_ALIASES, }, default: flagOptions.default, unknown: (name: string) => { diff --git a/packages/kbn-dev-cli-runner/src/flags_reader.test.ts b/packages/kbn-dev-cli-runner/src/flags_reader.test.ts new file mode 100644 index 0000000000000..bef3339c5b27a --- /dev/null +++ b/packages/kbn-dev-cli-runner/src/flags_reader.test.ts @@ -0,0 +1,344 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createAbsolutePathSerializer } from '@kbn/jest-serializers'; + +import { getFlags } from './flags'; +import { FlagsReader } from './flags_reader'; + +const FLAGS = { + string: 'string', + astring: ['foo', 'bar'], + num: '1234', + bool: true, + missing: undefined, +}; + +const basic = new FlagsReader(FLAGS); + +expect.addSnapshotSerializer(createAbsolutePathSerializer()); + +describe('#string()', () => { + it('returns a single string, regardless of flag count', () => { + expect(basic.string('string')).toMatchInlineSnapshot(`"string"`); + expect(basic.string('astring')).toBe(FLAGS.astring.at(-1)); + }); + + it('returns undefined when flag is missing', () => { + expect(basic.string('missing')).toMatchInlineSnapshot(`undefined`); + }); + + it('throws for non-string flags', () => { + expect(() => basic.string('bool')).toThrowErrorMatchingInlineSnapshot( + `"expected --bool to be a string"` + ); + }); + + describe('required version', () => { + it('throws when flag is missing', () => { + expect(() => basic.requiredString('missing')).toThrowErrorMatchingInlineSnapshot( + `"missing required flag --missing"` + ); + }); + }); +}); + +describe('#arrayOfStrings()', () => { + it('returns an array of strings for string flags, regardless of count', () => { + expect(basic.arrayOfStrings('string')).toMatchInlineSnapshot(` + Array [ + "string", + ] + `); + expect(basic.arrayOfStrings('astring')).toMatchInlineSnapshot(` + Array [ + "foo", + "bar", + ] + `); + }); + + it('returns undefined when flag is missing', () => { + expect(basic.arrayOfStrings('missing')).toMatchInlineSnapshot(`undefined`); + }); + + it('throws for non-string flags', () => { + expect(() => basic.arrayOfStrings('bool')).toThrowErrorMatchingInlineSnapshot( + `"expected --bool to be a string"` + ); + }); + + describe('required version', () => { + it('throws when flag is missing', () => { + expect(() => basic.requiredArrayOfStrings('missing')).toThrowErrorMatchingInlineSnapshot( + `"missing required flag --missing"` + ); + }); + }); +}); + +describe('#enum()', () => { + it('validates that values match options', () => { + expect(basic.enum('string', ['a', 'string', 'b'])).toMatchInlineSnapshot(`"string"`); + expect(basic.enum('missing', ['a', 'b'])).toMatchInlineSnapshot(`undefined`); + expect(() => basic.enum('string', ['a', 'b'])).toThrowErrorMatchingInlineSnapshot( + `"invalid --string, expected one of \\"a\\", \\"b\\""` + ); + }); +}); + +describe('#path()', () => { + it('parses the string to an absolute path based on CWD', () => { + expect(basic.path('string')).toMatchInlineSnapshot(`/string`); + expect(basic.path('missing')).toMatchInlineSnapshot(`undefined`); + }); + + describe('required version', () => { + it('throws if the flag is missing', () => { + expect(() => basic.requiredPath('missing')).toThrowErrorMatchingInlineSnapshot( + `"missing required flag --missing"` + ); + }); + }); + + describe('array version', () => { + it('parses a list of paths', () => { + expect(basic.arrayOfPaths('astring')).toMatchInlineSnapshot(` + Array [ + /foo, + /bar, + ] + `); + }); + + describe('required version', () => { + it('throws if the flag is missing', () => { + expect(() => basic.requiredArrayOfPaths('missing')).toThrowErrorMatchingInlineSnapshot( + `"missing required flag --missing"` + ); + }); + }); + }); +}); + +describe('#number()', () => { + it('parses strings as numbers', () => { + expect(basic.number('num')).toMatchInlineSnapshot(`1234`); + expect(basic.number('missing')).toMatchInlineSnapshot(`undefined`); + expect(() => basic.number('bool')).toThrowErrorMatchingInlineSnapshot( + `"expected --bool to be a string"` + ); + expect(() => basic.number('string')).toThrowErrorMatchingInlineSnapshot( + `"unable to parse --string value [string] as a number"` + ); + expect(() => basic.number('astring')).toThrowErrorMatchingInlineSnapshot( + `"unable to parse --astring value [bar] as a number"` + ); + }); + + describe('required version', () => { + it('throws if the flag is missing', () => { + expect(() => basic.requiredNumber('missing')).toThrowErrorMatchingInlineSnapshot( + `"missing required flag --missing"` + ); + }); + }); +}); + +describe('#boolean()', () => { + it('ensures flag is boolean, requires value', () => { + expect(basic.boolean('bool')).toMatchInlineSnapshot(`true`); + expect(() => basic.boolean('missing')).toThrowErrorMatchingInlineSnapshot( + `"expected --missing to be a boolean"` + ); + expect(() => basic.boolean('string')).toThrowErrorMatchingInlineSnapshot( + `"expected --string to be a boolean"` + ); + expect(() => basic.boolean('astring')).toThrowErrorMatchingInlineSnapshot( + `"expected --astring to be a boolean"` + ); + }); +}); + +describe('#getPositionals()', () => { + it('returns all positional arguments in flags', () => { + const flags = new FlagsReader({ + ...FLAGS, + _: ['a', 'b', 'c'], + }); + + expect(flags.getPositionals()).toMatchInlineSnapshot(` + Array [ + "a", + "b", + "c", + ] + `); + }); + + it('handles missing _ flag', () => { + const flags = new FlagsReader({}); + expect(flags.getPositionals()).toMatchInlineSnapshot(`Array []`); + }); +}); + +describe('#getUnused()', () => { + it('returns a map of all unused flags', () => { + const flags = new FlagsReader({ + a: '1', + b: '2', + c: '3', + }); + + expect(flags.getUnused()).toMatchInlineSnapshot(` + Map { + "a" => "1", + "b" => "2", + "c" => "3", + } + `); + + flags.number('a'); + flags.number('b'); + + expect(flags.getUnused()).toMatchInlineSnapshot(` + Map { + "c" => "3", + } + `); + }); + + it('ignores the default flags which are forced on commands', () => { + const rawFlags = getFlags(['--a=1'], { + string: ['a'], + }); + + const flags = new FlagsReader(rawFlags, { + aliases: { + v: 'verbose', + }, + }); + + expect(flags.getUnused()).toMatchInlineSnapshot(` + Map { + "a" => "1", + } + `); + flags.number('a'); + expect(flags.getUnused()).toMatchInlineSnapshot(`Map {}`); + }); + + it('treats aliased flags as used', () => { + const flags = new FlagsReader( + { + f: true, + force: true, + v: true, + verbose: true, + }, + { + aliases: { + f: 'force', + v: 'verbose', + }, + } + ); + + expect(flags.getUnused()).toMatchInlineSnapshot(` + Map { + "f" => true, + "force" => true, + } + `); + flags.boolean('force'); + expect(flags.getUnused()).toMatchInlineSnapshot(`Map {}`); + flags.boolean('v'); + expect(flags.getUnused()).toMatchInlineSnapshot(`Map {}`); + }); + + it('treats failed reads as "uses"', () => { + const flags = new FlagsReader({ a: 'b' }); + + expect(flags.getUnused()).toMatchInlineSnapshot(` + Map { + "a" => "b", + } + `); + expect(() => flags.number('a')).toThrowError(); + expect(flags.getUnused()).toMatchInlineSnapshot(`Map {}`); + }); +}); + +describe('#getUsed()', () => { + it('returns a map of all used flags', () => { + const flags = new FlagsReader({ + a: '1', + b: '2', + c: '3', + }); + + expect(flags.getUsed()).toMatchInlineSnapshot(`Map {}`); + + flags.number('a'); + flags.number('b'); + + expect(flags.getUsed()).toMatchInlineSnapshot(` + Map { + "a" => "1", + "b" => "2", + } + `); + }); + + it('treats aliases flags as used', () => { + const flags = new FlagsReader( + { + f: true, + force: true, + v: true, + verbose: true, + }, + { + aliases: { + f: 'force', + v: 'verbose', + }, + } + ); + + expect(flags.getUsed()).toMatchInlineSnapshot(`Map {}`); + flags.boolean('force'); + expect(flags.getUsed()).toMatchInlineSnapshot(` + Map { + "force" => true, + "f" => true, + } + `); + flags.boolean('v'); + expect(flags.getUsed()).toMatchInlineSnapshot(` + Map { + "force" => true, + "f" => true, + "v" => true, + "verbose" => true, + } + `); + }); + + it('treats failed reads as "uses"', () => { + const flags = new FlagsReader({ a: 'b' }); + + expect(flags.getUsed()).toMatchInlineSnapshot(`Map {}`); + expect(() => flags.number('a')).toThrowError(); + expect(flags.getUsed()).toMatchInlineSnapshot(` + Map { + "a" => "b", + } + `); + }); +}); diff --git a/packages/kbn-dev-cli-runner/src/flags_reader.ts b/packages/kbn-dev-cli-runner/src/flags_reader.ts new file mode 100644 index 0000000000000..156f1a4fba32b --- /dev/null +++ b/packages/kbn-dev-cli-runner/src/flags_reader.ts @@ -0,0 +1,267 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; + +import { createFlagError } from '@kbn/dev-cli-errors'; +import { LOG_LEVEL_FLAGS } from '@kbn/tooling-log'; + +type FlagValue = string | string[] | boolean; +const FORCED_FLAGS = new Set([...LOG_LEVEL_FLAGS.map((l) => l.name), 'help']); + +const makeAbsolute = (rel: string) => Path.resolve(process.cwd(), rel); + +const nonUndefinedValues = (e: [string, FlagValue | undefined]): e is [string, FlagValue] => + e[1] !== undefined; + +export class FlagsReader { + private readonly used: Map; + private readonly unused: Map; + private readonly _: string[]; + private readonly aliasMap: Map; + + constructor( + flags: Record, + private readonly opts?: { aliases?: Record } + ) { + this.used = new Map(); + this.unused = new Map( + Object.entries(flags) + .filter(nonUndefinedValues) + .filter((e) => e[0] !== 'unexpected') + ); + this.aliasMap = new Map( + Object.entries(this.opts?.aliases ?? []).flatMap(([a, b]) => [ + [a, b], + [b, a], + ]) + ); + + this._ = this.arrayOfStrings('_') ?? []; + } + + private use(key: string) { + const alias = this.aliasMap.get(key); + + const used = this.used.get(key); + if (used !== undefined) { + return used; + } + + const unused = this.unused.get(key); + if (unused !== undefined) { + this.used.set(key, unused); + this.unused.delete(key); + + if (alias !== undefined) { + this.used.set(alias, unused); + this.unused.delete(alias); + } + } + + return unused; + } + + /** + * Read a string flag that supports multiple instances into an array of strings. If the + * flag is only passed once an array with a single item will be returned. If the flag is not + * passed then undefined will be returned. + */ + arrayOfStrings(key: string) { + const value = this.use(key); + + switch (typeof value) { + case 'boolean': + throw createFlagError(`expected --${key} to be a string`); + case 'string': + return value ? [value] : []; + default: + return value; + } + } + + /** + * Same as #arrayOfStrings() except when the flag is not passed a "flag error" is thrown telling + * the user that the flag is required and shows them the help text. + */ + requiredArrayOfStrings(key: string) { + const value = this.arrayOfStrings(key); + if (value === undefined) { + throw createFlagError(`missing required flag --${key}`); + } + return value; + } + + /** + * Read the value of a string flag. If the flag is passed multiple times the last value is returned. If + * the flag is not passed then undefined is returned. + */ + string(key: string) { + const value = this.use(key); + + switch (typeof value) { + case 'undefined': + return undefined; + case 'string': + return value || undefined; // convert "" to undefined + case 'object': + const last = value.at(-1); + if (last === undefined) { + throw createFlagError(`expected --${key} to be a string`); + } + return last || undefined; // convert "" to undefined + default: + throw createFlagError(`expected --${key} to be a string`); + } + } + + /** + * Same as #string() except when the flag is passed it is validated against a list + * of valid values + */ + enum(key: string, values: readonly T[]) { + const value = this.string(key); + if (value === undefined) { + return; + } + + if (values.includes(value as T)) { + return value as T; + } + + throw createFlagError(`invalid --${key}, expected one of "${values.join('", "')}"`); + } + + /** + * Same as #string() except when a flag is not passed a "flag error" is thrown telling the user + * that the flag is required and shows them the help text. + */ + requiredString(key: string) { + const value = this.string(key); + if (value === undefined) { + throw createFlagError(`missing required flag --${key}`); + } + return value; + } + + /** + * Same as #string(), except that when there is a value for the string it is resolved to an + * absolute path based on the current working directory + */ + path(key: string) { + const value = this.string(key); + if (value !== undefined) { + return makeAbsolute(value); + } + } + + /** + * Same as #requiredString() except that values are converted to absolute paths based on the + * current working directory + */ + requiredPath(key: string) { + return makeAbsolute(this.requiredString(key)); + } + + /** + * Same as #arrayOfStrings(), except that when there are values they are resolved to + * absolute paths based on the current working directory + */ + arrayOfPaths(key: string) { + const value = this.arrayOfStrings(key); + if (value !== undefined) { + return value.map(makeAbsolute); + } + } + + /** + * Same as #requiredArrayOfStrings(), except that values are resolved to absolute paths + * based on the current working directory + */ + requiredArrayOfPaths(key: string) { + return this.requiredArrayOfStrings(key).map(makeAbsolute); + } + + /** + * Parsed the provided flag as a number, if the value does not parse to a valid number + * using Number.parseFloat() then a "flag error" is thrown. If the flag is not passed + * undefined is returned. + */ + number(key: string) { + const value = this.string(key); + if (value === undefined) { + return; + } + + const num = Number.parseFloat(value); + if (Number.isNaN(num)) { + throw createFlagError(`unable to parse --${key} value [${value}] as a number`); + } + + return num; + } + + /** + * Same as #number() except that when the flag is missing a "flag error" is thrown + */ + requiredNumber(key: string) { + const value = this.number(key); + if (value === undefined) { + throw createFlagError(`missing required flag --${key}`); + } + return value; + } + + /** + * Read a boolean flag value, if the flag is properly defined as a "boolean" in the run options + * then the value will always be a boolean, defaulting to `false`, so there is no need for an + * optional/requiredBoolean() method. + */ + boolean(key: string) { + const value = this.use(key); + if (typeof value !== 'boolean') { + throw createFlagError(`expected --${key} to be a boolean`); + } + return value; + } + + /** + * Get the positional arguments passed, includes any values that are not associated with + * a specific --flag + */ + getPositionals() { + return this._.slice(0); + } + + /** + * Returns all of the unused flags. When a flag is read via any of the key-specific methods + * the key is marked as "used" and this method will return a map of just the flags which + * have not been used yet (excluding the default flags like --debug, --verbose, and --help) + */ + getUnused() { + return new Map( + [...this.unused.entries()].filter(([key]) => { + const alias = this.aliasMap.get(key); + if (alias !== undefined && FORCED_FLAGS.has(alias)) { + return false; + } + + return !FORCED_FLAGS.has(key); + }) + ); + } + + /** + * Returns all of the used flags. When a flag is read via any of the key-specific methods + * the key is marked as "used" and from then on this method will return a map including that + * and any other key used by these methods. + */ + getUsed() { + return new Map(this.used); + } +} diff --git a/packages/kbn-dev-cli-runner/src/run.ts b/packages/kbn-dev-cli-runner/src/run.ts index bbccfdde564f8..08457caaebfd4 100644 --- a/packages/kbn-dev-cli-runner/src/run.ts +++ b/packages/kbn-dev-cli-runner/src/run.ts @@ -10,7 +10,8 @@ import { pickLevelFromFlags, ToolingLog, LogLevel } from '@kbn/tooling-log'; import { ProcRunner, withProcRunner } from '@kbn/dev-proc-runner'; import { createFlagError } from '@kbn/dev-cli-errors'; -import { Flags, getFlags, FlagOptions } from './flags'; +import { Flags, getFlags, FlagOptions, DEFAULT_FLAG_ALIASES } from './flags'; +import { FlagsReader } from './flags_reader'; import { getHelp } from './help'; import { CleanupTask, Cleanup } from './cleanup'; import { Metrics, MetricsMeta } from './metrics'; @@ -21,6 +22,7 @@ export interface RunContext { procRunner: ProcRunner; statsMeta: MetricsMeta; addCleanupTask: (task: CleanupTask) => void; + flagsReader: FlagsReader; } export type RunFn = (context: RunContext) => Promise | void; @@ -71,6 +73,12 @@ export async function run(fn: RunFn, options: RunOptions = {}) { procRunner, statsMeta: metrics.meta, addCleanupTask: cleanup.add.bind(cleanup), + flagsReader: new FlagsReader(flags, { + aliases: { + ...options.flags?.alias, + ...DEFAULT_FLAG_ALIASES, + }, + }), }); }); } catch (error) { diff --git a/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts b/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts index c740087b40c30..329e858b08f5e 100644 --- a/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts +++ b/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts @@ -9,6 +9,7 @@ import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log'; import { ProcRunner } from '@kbn/dev-proc-runner'; +import { FlagsReader } from './flags_reader'; import { RunWithCommands } from './run_with_commands'; const testLog = new ToolingLog(); @@ -44,6 +45,7 @@ it('extends the context using extendContext()', async () => { expect(context).toEqual({ log: expect.any(ToolingLog), flags: expect.any(Object), + flagsReader: expect.any(FlagsReader), addCleanupTask: expect.any(Function), procRunner: expect.any(ProcRunner), statsMeta: expect.any(Map), diff --git a/packages/kbn-dev-cli-runner/src/run_with_commands.ts b/packages/kbn-dev-cli-runner/src/run_with_commands.ts index 94b167671d21b..ff93f29f4c631 100644 --- a/packages/kbn-dev-cli-runner/src/run_with_commands.ts +++ b/packages/kbn-dev-cli-runner/src/run_with_commands.ts @@ -11,7 +11,8 @@ import { withProcRunner } from '@kbn/dev-proc-runner'; import { createFlagError } from '@kbn/dev-cli-errors'; import { RunContext, RunOptions } from './run'; -import { getFlags, FlagOptions, mergeFlagOptions } from './flags'; +import { getFlags, FlagOptions, mergeFlagOptions, DEFAULT_FLAG_ALIASES } from './flags'; +import { FlagsReader } from './flags_reader'; import { Cleanup } from './cleanup'; import { getHelpForAllCommands, getCommandLevelHelp } from './help'; import { Metrics } from './metrics'; @@ -116,6 +117,12 @@ export class RunWithCommands { procRunner, statsMeta: metrics.meta, addCleanupTask: cleanup.add.bind(cleanup), + flagsReader: new FlagsReader(commandFlags, { + aliases: { + ...commandFlagOptions.alias, + ...DEFAULT_FLAG_ALIASES, + }, + }), }; const extendedContext = { diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 413e8905bea93..8c39a15033ce1 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -447,6 +447,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { monitorUptimeSynthetics: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/monitor-uptime-synthetics.html`, userExperience: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/user-experience.html`, createAlerts: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/create-alerts.html`, + syntheticsCommandReference: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetics-configuration.html#synthetics-configuration-playwright-options`, }, alerting: { guide: `${KIBANA_DOCS}create-and-manage-rules.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 1ee0d5414b275..3735c1abbddbf 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -331,6 +331,7 @@ export interface DocLinks { monitorUptimeSynthetics: string; userExperience: string; createAlerts: string; + syntheticsCommandReference: string; }>; readonly alerting: Record; readonly maps: Readonly<{ diff --git a/packages/kbn-es-query/src/filters/helpers/meta_filter.ts b/packages/kbn-es-query/src/filters/helpers/meta_filter.ts index 484b85d608cff..3406ad5a5a1ce 100644 --- a/packages/kbn-es-query/src/filters/helpers/meta_filter.ts +++ b/packages/kbn-es-query/src/filters/helpers/meta_filter.ts @@ -113,11 +113,7 @@ export const unpinFilter = (filter: Filter) => * @public */ export const isFilter = (x: unknown): x is Filter => - !!x && - typeof x === 'object' && - !!(x as Filter).meta && - typeof (x as Filter).meta === 'object' && - typeof (x as Filter).meta.disabled === 'boolean'; + !!x && typeof x === 'object' && !!(x as Filter).meta && typeof (x as Filter).meta === 'object'; /** * @param {unknown} filters diff --git a/packages/kbn-failed-test-reporter-cli/BUILD.bazel b/packages/kbn-failed-test-reporter-cli/BUILD.bazel new file mode 100644 index 0000000000000..a3ae8903169a3 --- /dev/null +++ b/packages/kbn-failed-test-reporter-cli/BUILD.bazel @@ -0,0 +1,145 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "kbn-failed-test-reporter-cli" +PKG_REQUIRE_NAME = "@kbn/failed-test-reporter-cli" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.html", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "//packages/kbn-utils:npm_module_types", + "//packages/kbn-ci-stats-reporter:npm_module_types", + "//packages/kbn-dev-cli-runner:npm_module_types", + "//packages/kbn-dev-cli-errors:npm_module_types", + "//packages/kbn-dev-utils:npm_module_types", + "//packages/kbn-tooling-log:npm_module_types", + "//packages/kbn-ftr-screenshot-filename:npm_module_types", + "//packages/kbn-jest-serializers:npm_module_types", + "//packages/kbn-journeys:npm_module_types", + "@npm//@elastic/elasticsearch", + "@npm//@types/node", + "@npm//@types/he", + "@npm//@types/jest", + "@npm//@types/strip-ansi", + "@npm//@types/normalize-path", + "@npm//@types/xml2js", + "@npm//axios", + "@npm//dedent", + "@npm//globby", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), + additional_args = [ + "--copy-files" + ], +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-failed-test-reporter-cli/README.md b/packages/kbn-failed-test-reporter-cli/README.md new file mode 100644 index 0000000000000..d577a58dfb856 --- /dev/null +++ b/packages/kbn-failed-test-reporter-cli/README.md @@ -0,0 +1,3 @@ +# @kbn/failed-test-reporter-cli + +Empty package generated by @kbn/generate diff --git a/packages/kbn-test/src/failed_tests_reporter/README.md b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/README.md similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/README.md rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/README.md diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/cypress_report.xml b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/cypress_report.xml similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/__fixtures__/cypress_report.xml rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/cypress_report.xml diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/ftr_report.xml b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/ftr_report.xml similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/__fixtures__/ftr_report.xml rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/ftr_report.xml diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/index.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/index.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/jest_report.xml b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/jest_report.xml similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/__fixtures__/jest_report.xml rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/jest_report.xml diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/mocha_report.xml b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/mocha_report.xml similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/__fixtures__/mocha_report.xml rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/mocha_report.xml diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/add_messages_to_report.test.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/add_messages_to_report.test.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/add_messages_to_report.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/add_messages_to_report.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/buildkite_metadata.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/buildkite_metadata.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/buildkite_metadata.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/buildkite_metadata.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/es_config b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/es_config similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/es_config rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/es_config diff --git a/packages/kbn-test/src/failed_tests_reporter/existing_failed_test_issues.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/existing_failed_test_issues.test.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/existing_failed_test_issues.test.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/existing_failed_test_issues.test.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/existing_failed_test_issues.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/existing_failed_test_issues.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/existing_failed_test_issues.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/existing_failed_test_issues.ts diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts new file mode 100644 index 0000000000000..b105b6d80ac37 --- /dev/null +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts @@ -0,0 +1,208 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; + +import { REPO_ROOT } from '@kbn/utils'; +import { run } from '@kbn/dev-cli-runner'; +import { createFailError, createFlagError } from '@kbn/dev-cli-errors'; +import { CiStatsReporter } from '@kbn/ci-stats-reporter'; +import globby from 'globby'; +import normalize from 'normalize-path'; + +import { getFailures } from './get_failures'; +import { GithubApi } from './github_api'; +import { updateFailureIssue, createFailureIssue } from './report_failure'; +import { readTestReport, getRootMetadata } from './test_report'; +import { addMessagesToReport } from './add_messages_to_report'; +import { getReportMessageIter } from './report_metadata'; +import { reportFailuresToEs } from './report_failures_to_es'; +import { reportFailuresToFile } from './report_failures_to_file'; +import { getBuildkiteMetadata } from './buildkite_metadata'; +import { ExistingFailedTestIssues } from './existing_failed_test_issues'; + +const DEFAULT_PATTERNS = [Path.resolve(REPO_ROOT, 'target/junit/**/*.xml')]; +const DISABLE_MISSING_TEST_REPORT_ERRORS = + process.env.DISABLE_MISSING_TEST_REPORT_ERRORS === 'true'; + +run( + async ({ log, flags }) => { + const indexInEs = flags['index-errors']; + + let updateGithub = flags['github-update']; + if (updateGithub && !process.env.GITHUB_TOKEN) { + throw createFailError( + 'GITHUB_TOKEN environment variable must be set, otherwise use --no-github-update flag' + ); + } + + let branch: string = ''; + if (updateGithub) { + let isPr = false; + + if (process.env.BUILDKITE === 'true') { + branch = process.env.BUILDKITE_BRANCH || ''; + isPr = process.env.BUILDKITE_PULL_REQUEST === 'true'; + updateGithub = process.env.REPORT_FAILED_TESTS_TO_GITHUB === 'true'; + } else { + // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others + const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); + branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH || ''; + isPr = !!process.env.ghprbPullId; + + const isMainOrVersion = branch === 'main' || branch.match(/^\d+\.(x|\d+)$/); + if (!isMainOrVersion || isPr) { + log.info('Failure issues only created on main/version branch jobs'); + updateGithub = false; + } + } + + if (!branch) { + throw createFailError( + 'Unable to determine originating branch from job name or other environment variables' + ); + } + } + + const githubApi = new GithubApi({ + log, + token: process.env.GITHUB_TOKEN, + dryRun: !updateGithub, + }); + + const bkMeta = getBuildkiteMetadata(); + + try { + const buildUrl = flags['build-url'] || (updateGithub ? '' : 'http://buildUrl'); + if (typeof buildUrl !== 'string' || !buildUrl) { + throw createFlagError('Missing --build-url or process.env.BUILD_URL'); + } + + const patterns = (flags._.length ? flags._ : DEFAULT_PATTERNS).map((p) => + normalize(Path.resolve(p)) + ); + log.info('Searching for reports at', patterns); + const reportPaths = await globby(patterns, { + absolute: true, + }); + + if (!reportPaths.length && DISABLE_MISSING_TEST_REPORT_ERRORS) { + // it is fine for code coverage to not have test results + return; + } + + if (!reportPaths.length) { + throw createFailError(`Unable to find any junit reports with patterns [${patterns}]`); + } + + log.info('found', reportPaths.length, 'junit reports', reportPaths); + + const existingIssues = new ExistingFailedTestIssues(log); + for (const reportPath of reportPaths) { + const report = await readTestReport(reportPath); + const messages = Array.from(getReportMessageIter(report)); + const failures = getFailures(report); + + await existingIssues.loadForFailures(failures); + + if (indexInEs) { + await reportFailuresToEs(log, failures); + } + + for (const failure of failures) { + const pushMessage = (msg: string) => { + messages.push({ + classname: failure.classname, + name: failure.name, + message: msg, + }); + }; + + if (failure.likelyIrrelevant) { + pushMessage( + 'Failure is likely irrelevant' + + (updateGithub ? ', so an issue was not created or updated' : '') + ); + continue; + } + + const existingIssue = existingIssues.getForFailure(failure); + if (existingIssue) { + const { newBody, newCount } = await updateFailureIssue( + buildUrl, + existingIssue, + githubApi, + branch + ); + const url = existingIssue.github.htmlUrl; + existingIssue.github.body = newBody; + failure.githubIssue = url; + failure.failureCount = updateGithub ? newCount : newCount - 1; + pushMessage(`Test has failed ${newCount - 1} times on tracked branches: ${url}`); + if (updateGithub) { + pushMessage(`Updated existing issue: ${url} (fail count: ${newCount})`); + } + continue; + } + + const newIssue = await createFailureIssue(buildUrl, failure, githubApi, branch); + existingIssues.addNewlyCreated(failure, newIssue); + pushMessage('Test has not failed recently on tracked branches'); + if (updateGithub) { + pushMessage(`Created new issue: ${newIssue.html_url}`); + failure.githubIssue = newIssue.html_url; + } + failure.failureCount = updateGithub ? 1 : 0; + } + + // mutates report to include messages and writes updated report to disk + await addMessagesToReport({ + report, + messages, + log, + reportPath, + dryRun: !flags['report-update'], + }); + + await reportFailuresToFile(log, failures, bkMeta, getRootMetadata(report)); + } + } finally { + await CiStatsReporter.fromEnv(log).metrics([ + { + group: 'github api request count', + id: `failed test reporter`, + value: githubApi.getRequestCount(), + meta: Object.fromEntries( + Object.entries(bkMeta).map( + ([k, v]) => [`buildkite${k[0].toUpperCase()}${k.slice(1)}`, v] as const + ) + ), + }, + ]); + } + }, + { + description: `a cli that opens issues or updates existing issues based on junit reports`, + flags: { + boolean: ['github-update', 'report-update'], + string: ['build-url'], + default: { + 'github-update': true, + 'report-update': true, + 'index-errors': true, + 'build-url': process.env.BUILD_URL, + }, + help: ` + --no-github-update Execute the CLI without writing to Github + --no-report-update Execute the CLI without writing to the JUnit reports + --no-index-errors Execute the CLI without indexing failures into Elasticsearch + --build-url URL of the failed build, defaults to process.env.BUILD_URL + `, + }, + } +); diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.test.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.test.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/get_failures.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/github_api.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/github_api.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/github_api.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/github_api.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/issue_metadata.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/issue_metadata.test.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/issue_metadata.test.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/issue_metadata.test.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/issue_metadata.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/issue_metadata.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/issue_metadata.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/issue_metadata.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.test.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.test.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/report_failure.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failure.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failures_to_es.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_es.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/report_failures_to_es.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_es.ts diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts new file mode 100644 index 0000000000000..d34df80f3d0a8 --- /dev/null +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import Fs from 'fs'; +import { createHash } from 'crypto'; + +import globby from 'globby'; +import { ToolingLog } from '@kbn/tooling-log'; +import { REPO_ROOT } from '@kbn/utils'; +import { escape } from 'he'; +import { FtrScreenshotFilename } from '@kbn/ftr-screenshot-filename'; +import { JourneyScreenshots } from '@kbn/journeys'; + +import { BuildkiteMetadata } from './buildkite_metadata'; +import { TestFailure } from './get_failures'; + +interface JourneyMeta { + journeyName: string; +} +function getJourneyMetadata(rootMeta: Record): JourneyMeta | undefined { + const { journeyName } = rootMeta; + if (typeof journeyName === 'string') { + return { journeyName }; + } + + return undefined; +} + +async function getJourneySnapshotHtml(log: ToolingLog, journeyMeta: JourneyMeta) { + let screenshots; + try { + screenshots = await JourneyScreenshots.load(journeyMeta.journeyName); + } catch (error) { + log.error(`Failed to load journey screenshots: ${error.message}`); + return ''; + } + + return [ + '
', + '
Steps
', + ...screenshots.get().flatMap(({ title, path }) => { + const base64 = Fs.readFileSync(path, 'base64'); + + return [ + `

${escape(title)}

`, + ``, + ]; + }), + '
', + ].join('\n'); +} + +let _allScreenshotsCache: Array<{ path: string; name: string }> | undefined; +function getAllScreenshots(log: ToolingLog) { + return (_allScreenshotsCache ??= findAllScreenshots(log)); +} +function findAllScreenshots(log: ToolingLog) { + try { + return globby + .sync( + [ + 'test/functional/**/screenshots/failure/*.png', + 'x-pack/test/functional/**/screenshots/failure/*.png', + ], + { + cwd: REPO_ROOT, + absolute: true, + } + ) + .map((path) => ({ + path, + name: Path.basename(path, Path.extname(path)), + })); + } catch (error) { + log.error(`Failed to find screenshots: ${error.message}`); + return []; + } +} + +function getFtrScreenshotHtml(log: ToolingLog, failureName: string) { + return getAllScreenshots(log) + .filter((s) => s.name.startsWith(FtrScreenshotFilename.create(failureName, { ext: false }))) + .map((s) => { + const base64 = Fs.readFileSync(s.path).toString('base64'); + return ``; + }) + .join('\n'); +} + +export async function reportFailuresToFile( + log: ToolingLog, + failures: TestFailure[], + bkMeta: BuildkiteMetadata, + rootMeta: Record +) { + if (!failures?.length) { + return; + } + + const journeyMeta = getJourneyMetadata(rootMeta); + + // Jest could, in theory, fail 1000s of tests and write 1000s of failures + // So let's just write files for the first 20 + for (const failure of failures.slice(0, 20)) { + const hash = createHash('md5').update(failure.name).digest('hex'); + const filenameBase = `${ + process.env.BUILDKITE_JOB_ID ? process.env.BUILDKITE_JOB_ID + '_' : '' + }${hash}`; + const dir = Path.join('target', 'test_failures'); + + const failureLog = [ + ['Test:', '-----', failure.classname, failure.name, ''], + ['Failure:', '--------', failure.failure], + failure['system-out'] ? ['', 'Standard Out:', '-------------', failure['system-out']] : [], + ] + .flat() + .join('\n'); + + const failureJSON = JSON.stringify( + { + ...failure, + hash, + buildId: bkMeta.buildId, + jobId: bkMeta.jobId, + url: bkMeta.url, + jobUrl: bkMeta.jobUrl, + jobName: bkMeta.jobName, + }, + null, + 2 + ); + + const failureHTML = Fs.readFileSync( + require.resolve('./report_failures_to_file_html_template.html') + ) + .toString() + .replace('$TITLE', escape(failure.name)) + .replace( + '$MAIN', + ` + ${failure.classname + .split('.') + .map((part) => `
${escape(part.replace('·', '.'))}
`) + .join('')} +
+

${escape(failure.name)}

+

+ + Failures in tracked branches: ${ + failure.failureCount || 0 + } + ${ + failure.githubIssue + ? `
${escape( + failure.githubIssue + )}` + : '' + } +
+

+ ${ + bkMeta.jobUrl + ? `

+ + Buildkite Job
+ ${escape(bkMeta.jobUrl)} +
+

` + : '' + } +
${escape(failure.failure)}
+ ${ + journeyMeta + ? await getJourneySnapshotHtml(log, journeyMeta) + : getFtrScreenshotHtml(log, failure.name) + } + ${ + failure['system-out'] + ? ` +
Stdout
+
${escape(failure['system-out'] || '')}
+ ` + : '' + } + ` + ); + + Fs.mkdirSync(dir, { recursive: true }); + Fs.writeFileSync(Path.join(dir, `${filenameBase}.log`), failureLog, 'utf8'); + Fs.writeFileSync(Path.join(dir, `${filenameBase}.html`), failureHTML, 'utf8'); + Fs.writeFileSync(Path.join(dir, `${filenameBase}.json`), failureJSON, 'utf8'); + } +} diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file_html_template.html b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file_html_template.html similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/report_failures_to_file_html_template.html rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file_html_template.html diff --git a/packages/kbn-test/src/failed_tests_reporter/report_metadata.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_metadata.test.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/report_metadata.test.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_metadata.test.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/report_metadata.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_metadata.ts similarity index 100% rename from packages/kbn-test/src/failed_tests_reporter/report_metadata.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_metadata.ts diff --git a/packages/kbn-test/src/failed_tests_reporter/test_report.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/test_report.ts similarity index 84% rename from packages/kbn-test/src/failed_tests_reporter/test_report.ts rename to packages/kbn-failed-test-reporter-cli/failed_tests_reporter/test_report.ts index 9c83d77b19a99..e70aa44a2a088 100644 --- a/packages/kbn-test/src/failed_tests_reporter/test_report.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/test_report.ts @@ -35,6 +35,8 @@ export interface TestSuite { failures: string; /* number of skipped tests as a string */ skipped: string; + /* optional JSON encoded metadata */ + 'metadata-json'?: string; }; testcase?: TestCase[]; } @@ -93,3 +95,22 @@ export function* makeFailedTestCaseIter(report: TestReport) { yield testCase as FailedTestCase; } } + +export function getRootMetadata(report: TestReport): Record { + const json = + ('testsuites' in report + ? report.testsuites?.testsuite?.[0]?.$?.['metadata-json'] + : report.testsuite?.$?.['metadata-json']) ?? '{}'; + + try { + const obj = JSON.parse(json); + + if (typeof obj === 'object' && obj !== null) { + return obj; + } + + return {}; + } catch { + return {}; + } +} diff --git a/packages/kbn-test/src/failed_tests_reporter/index.ts b/packages/kbn-failed-test-reporter-cli/index.ts similarity index 82% rename from packages/kbn-test/src/failed_tests_reporter/index.ts rename to packages/kbn-failed-test-reporter-cli/index.ts index b750cf44348e1..999da20da72f5 100644 --- a/packages/kbn-test/src/failed_tests_reporter/index.ts +++ b/packages/kbn-failed-test-reporter-cli/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { runFailedTestsReporterCli } from './run_failed_tests_reporter_cli'; +import './failed_tests_reporter/failed_tests_reporter_cli'; diff --git a/packages/kbn-test/src/functional_tests/cli/index.js b/packages/kbn-failed-test-reporter-cli/jest.config.js similarity index 56% rename from packages/kbn-test/src/functional_tests/cli/index.js rename to packages/kbn-failed-test-reporter-cli/jest.config.js index 9721d70d12262..eb33f488f9e84 100644 --- a/packages/kbn-test/src/functional_tests/cli/index.js +++ b/packages/kbn-failed-test-reporter-cli/jest.config.js @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -export { runTestsCli } from './run_tests/cli'; -export { processOptions as processRunTestsCliOptions } from './run_tests/args'; -export { startServersCli } from './start_servers/cli'; -export { processOptions as processStartServersCliOptions } from './start_servers/args'; +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-failed-test-reporter-cli'], +}; diff --git a/packages/kbn-failed-test-reporter-cli/kibana.jsonc b/packages/kbn-failed-test-reporter-cli/kibana.jsonc new file mode 100644 index 0000000000000..dfaa875e12735 --- /dev/null +++ b/packages/kbn-failed-test-reporter-cli/kibana.jsonc @@ -0,0 +1,8 @@ +{ + "type": "shared-common", + "id": "@kbn/failed-test-reporter-cli", + "owner": "@elastic/kibana-operations", + "devOnly": true, + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/kbn-failed-test-reporter-cli/package.json b/packages/kbn-failed-test-reporter-cli/package.json new file mode 100644 index 0000000000000..daf9a58cd77d7 --- /dev/null +++ b/packages/kbn-failed-test-reporter-cli/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/failed-test-reporter-cli", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-failed-test-reporter-cli/tsconfig.json b/packages/kbn-failed-test-reporter-cli/tsconfig.json new file mode 100644 index 0000000000000..81935b1385550 --- /dev/null +++ b/packages/kbn-failed-test-reporter-cli/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/kbn-ftr-common-functional-services/BUILD.bazel b/packages/kbn-ftr-common-functional-services/BUILD.bazel new file mode 100644 index 0000000000000..8085c75af4af1 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/BUILD.bazel @@ -0,0 +1,127 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "kbn-ftr-common-functional-services" +PKG_REQUIRE_NAME = "@kbn/ftr-common-functional-services" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-tooling-log:npm_module_types", + "//packages/kbn-es-archiver:npm_module_types", + "//packages/kbn-test:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-ftr-common-functional-services/README.md b/packages/kbn-ftr-common-functional-services/README.md new file mode 100644 index 0000000000000..a2438327a62f7 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/README.md @@ -0,0 +1,3 @@ +# @kbn/ftr-common-functional-services + +A collection of very common services used by all functional FTR configs, moved to a package so that we can start putting FTR configs in packages. \ No newline at end of file diff --git a/packages/kbn-ftr-common-functional-services/index.ts b/packages/kbn-ftr-common-functional-services/index.ts new file mode 100644 index 0000000000000..950a860f7553f --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ProvidedType } from '@kbn/test'; +export { services as commonFunctionalServices } from './services/all'; + +import { KibanaServerProvider } from './services/kibana_server'; +export type KibanaServer = ProvidedType; + +export { RetryService } from './services/retry'; + +import { EsArchiverProvider } from './services/es_archiver'; +export type EsArchiver = ProvidedType; + +import { EsProvider } from './services/es'; +export type Es = ProvidedType; diff --git a/packages/kbn-ftr-common-functional-services/jest.config.js b/packages/kbn-ftr-common-functional-services/jest.config.js new file mode 100644 index 0000000000000..1831bdb22630d --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-ftr-common-functional-services'], +}; diff --git a/packages/kbn-ftr-common-functional-services/kibana.jsonc b/packages/kbn-ftr-common-functional-services/kibana.jsonc new file mode 100644 index 0000000000000..5ceecdcda8610 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/kibana.jsonc @@ -0,0 +1,8 @@ +{ + "type": "shared-common", + "id": "@kbn/ftr-common-functional-services", + "owner": "@elastic/kibana-operations", + "devOnly": true, + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/kbn-ftr-common-functional-services/package.json b/packages/kbn-ftr-common-functional-services/package.json new file mode 100644 index 0000000000000..642a5a39c7141 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/ftr-common-functional-services", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-ftr-common-functional-services/services/all.ts b/packages/kbn-ftr-common-functional-services/services/all.ts new file mode 100644 index 0000000000000..14019caaa582c --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/all.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 { EsArchiverProvider } from './es_archiver'; +import { EsProvider } from './es'; +import { KibanaServerProvider } from './kibana_server'; +import { RetryService } from './retry'; + +export const services = { + es: EsProvider, + kibanaServer: KibanaServerProvider, + esArchiver: EsArchiverProvider, + retry: RetryService, +}; diff --git a/test/common/services/elasticsearch.ts b/packages/kbn-ftr-common-functional-services/services/es.ts similarity index 75% rename from test/common/services/elasticsearch.ts rename to packages/kbn-ftr-common-functional-services/services/es.ts index 2f19bfe9105d0..fe9aafbf10736 100644 --- a/test/common/services/elasticsearch.ts +++ b/packages/kbn-ftr-common-functional-services/services/es.ts @@ -9,12 +9,9 @@ import { Client } from '@elastic/elasticsearch'; import { systemIndicesSuperuser, createEsClientForFtrConfig } from '@kbn/test'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrProviderContext } from './ftr_provider_context'; -/* - registers Kibana-specific @elastic/elasticsearch client instance. - */ -export function ElasticsearchProvider({ getService }: FtrProviderContext): Client { +export function EsProvider({ getService }: FtrProviderContext): Client { const config = getService('config'); return createEsClientForFtrConfig(config, { diff --git a/test/common/services/es_archiver.ts b/packages/kbn-ftr-common-functional-services/services/es_archiver.ts similarity index 61% rename from test/common/services/es_archiver.ts rename to packages/kbn-ftr-common-functional-services/services/es_archiver.ts index 865c2ba4b4434..8a81297bf1784 100644 --- a/test/common/services/es_archiver.ts +++ b/packages/kbn-ftr-common-functional-services/services/es_archiver.ts @@ -7,17 +7,15 @@ */ import { EsArchiver } from '@kbn/es-archiver'; -import { FtrProviderContext } from '../ftr_provider_context'; -import * as KibanaServer from './kibana_server'; +import { FtrProviderContext } from './ftr_provider_context'; +import { extendEsArchiver } from './kibana_server'; export function EsArchiverProvider({ getService }: FtrProviderContext): EsArchiver { const config = getService('config'); const client = getService('es'); - const lifecycle = getService('lifecycle'); const log = getService('log'); const kibanaServer = getService('kibanaServer'); const retry = getService('retry'); - const esArchives: string[] = config.get('testData.esArchives'); const esArchiver = new EsArchiver({ client, @@ -25,26 +23,12 @@ export function EsArchiverProvider({ getService }: FtrProviderContext): EsArchiv kbnClient: kibanaServer, }); - KibanaServer.extendEsArchiver({ + extendEsArchiver({ esArchiver, kibanaServer, retry, defaults: config.get('uiSettings.defaults'), }); - if (esArchives.length) { - lifecycle.beforeTests.add(async () => { - for (const archive of esArchives) { - await esArchiver.load(archive); - } - }); - - lifecycle.cleanup.add(async () => { - for (const archive of esArchives) { - await esArchiver.unload(archive); - } - }); - } - return esArchiver; } diff --git a/packages/kbn-ftr-common-functional-services/services/ftr_provider_context.ts b/packages/kbn-ftr-common-functional-services/services/ftr_provider_context.ts new file mode 100644 index 0000000000000..979658fbd8edd --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/ftr_provider_context.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; + +import type { services } from './all'; + +type Services = typeof services; + +export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/test/common/services/kibana_server/extend_es_archiver.ts b/packages/kbn-ftr-common-functional-services/services/kibana_server/extend_es_archiver.ts similarity index 100% rename from test/common/services/kibana_server/extend_es_archiver.ts rename to packages/kbn-ftr-common-functional-services/services/kibana_server/extend_es_archiver.ts diff --git a/test/common/services/kibana_server/index.ts b/packages/kbn-ftr-common-functional-services/services/kibana_server/index.ts similarity index 100% rename from test/common/services/kibana_server/index.ts rename to packages/kbn-ftr-common-functional-services/services/kibana_server/index.ts diff --git a/test/common/services/kibana_server/kibana_server.ts b/packages/kbn-ftr-common-functional-services/services/kibana_server/kibana_server.ts similarity index 69% rename from test/common/services/kibana_server/kibana_server.ts rename to packages/kbn-ftr-common-functional-services/services/kibana_server/kibana_server.ts index 182b289ed1d8e..bdfc42670f18b 100644 --- a/test/common/services/kibana_server/kibana_server.ts +++ b/packages/kbn-ftr-common-functional-services/services/kibana_server/kibana_server.ts @@ -9,7 +9,7 @@ import Url from 'url'; import { KbnClient } from '@kbn/test'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../ftr_provider_context'; export function KibanaServerProvider({ getService }: FtrProviderContext): KbnClient { const log = getService('log'); @@ -17,7 +17,6 @@ export function KibanaServerProvider({ getService }: FtrProviderContext): KbnCli const lifecycle = getService('lifecycle'); const url = Url.format(config.get('servers.kibana')); const defaults = config.get('uiSettings.defaults'); - const kbnArchives: string[] = config.get('testData.kbnArchives'); const kbn = new KbnClient({ log, @@ -32,18 +31,5 @@ export function KibanaServerProvider({ getService }: FtrProviderContext): KbnCli }); } - if (kbnArchives.length) { - lifecycle.beforeTests.add(async () => { - for (const archive of kbnArchives) { - await kbn.importExport.load(archive); - } - }); - lifecycle.cleanup.add(async () => { - for (const archive of kbnArchives) { - await kbn.importExport.unload(archive); - } - }); - } - return kbn; } diff --git a/test/common/services/retry/index.ts b/packages/kbn-ftr-common-functional-services/services/retry/index.ts similarity index 100% rename from test/common/services/retry/index.ts rename to packages/kbn-ftr-common-functional-services/services/retry/index.ts diff --git a/test/common/services/retry/retry.ts b/packages/kbn-ftr-common-functional-services/services/retry/retry.ts similarity index 96% rename from test/common/services/retry/retry.ts rename to packages/kbn-ftr-common-functional-services/services/retry/retry.ts index 5c823e256ddc8..231a829225dbc 100644 --- a/test/common/services/retry/retry.ts +++ b/packages/kbn-ftr-common-functional-services/services/retry/retry.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FtrService } from '../../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { retryForSuccess } from './retry_for_success'; import { retryForTruthy } from './retry_for_truthy'; diff --git a/test/common/services/retry/retry_for_success.ts b/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts similarity index 100% rename from test/common/services/retry/retry_for_success.ts rename to packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts diff --git a/test/common/services/retry/retry_for_truthy.ts b/packages/kbn-ftr-common-functional-services/services/retry/retry_for_truthy.ts similarity index 100% rename from test/common/services/retry/retry_for_truthy.ts rename to packages/kbn-ftr-common-functional-services/services/retry/retry_for_truthy.ts diff --git a/packages/kbn-ftr-common-functional-services/tsconfig.json b/packages/kbn-ftr-common-functional-services/tsconfig.json new file mode 100644 index 0000000000000..81935b1385550 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/kbn-ftr-screenshot-filename/BUILD.bazel b/packages/kbn-ftr-screenshot-filename/BUILD.bazel new file mode 100644 index 0000000000000..5cbd3e2c87ac7 --- /dev/null +++ b/packages/kbn-ftr-screenshot-filename/BUILD.bazel @@ -0,0 +1,125 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "kbn-ftr-screenshot-filename" +PKG_REQUIRE_NAME = "@kbn/ftr-screenshot-filename" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//tslib", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-ftr-screenshot-filename/README.md b/packages/kbn-ftr-screenshot-filename/README.md new file mode 100644 index 0000000000000..faf5d4b994e50 --- /dev/null +++ b/packages/kbn-ftr-screenshot-filename/README.md @@ -0,0 +1,3 @@ +# @kbn/ftr-screenshot-filename + +A simple package that exposes a helper function for generating a unique screenshot filename that can be found by `node scripts/failed_test_reporter`. diff --git a/packages/kbn-ftr-screenshot-filename/ftr_screenshot_filename.ts b/packages/kbn-ftr-screenshot-filename/ftr_screenshot_filename.ts new file mode 100644 index 0000000000000..2cce021d50826 --- /dev/null +++ b/packages/kbn-ftr-screenshot-filename/ftr_screenshot_filename.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 { createHash } from 'crypto'; + +export function create(fullTitle: string, opts?: { ext?: boolean }) { + const truncatedName = fullTitle.replaceAll(/[^ a-zA-Z0-9-]+/g, '').slice(0, 80); + const failureNameHash = createHash('sha256').update(fullTitle).digest('hex'); + return `${truncatedName}-${failureNameHash}${opts?.ext === false ? '' : `.png`}`; +} diff --git a/packages/kbn-ftr-screenshot-filename/index.ts b/packages/kbn-ftr-screenshot-filename/index.ts new file mode 100644 index 0000000000000..2911989659805 --- /dev/null +++ b/packages/kbn-ftr-screenshot-filename/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as FtrScreenshotFilename from './ftr_screenshot_filename'; + +export { FtrScreenshotFilename }; diff --git a/packages/kbn-ftr-screenshot-filename/jest.config.js b/packages/kbn-ftr-screenshot-filename/jest.config.js new file mode 100644 index 0000000000000..0ab6eb759a1dd --- /dev/null +++ b/packages/kbn-ftr-screenshot-filename/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-ftr-screenshot-filename'], +}; diff --git a/packages/kbn-ftr-screenshot-filename/kibana.jsonc b/packages/kbn-ftr-screenshot-filename/kibana.jsonc new file mode 100644 index 0000000000000..61ce39de5a622 --- /dev/null +++ b/packages/kbn-ftr-screenshot-filename/kibana.jsonc @@ -0,0 +1,8 @@ +{ + "type": "shared-common", + "id": "@kbn/ftr-screenshot-filename", + "owner": "@elastic/kibana-operations", + "devOnly": true, + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/kbn-ftr-screenshot-filename/package.json b/packages/kbn-ftr-screenshot-filename/package.json new file mode 100644 index 0000000000000..8e3a9b1e57db4 --- /dev/null +++ b/packages/kbn-ftr-screenshot-filename/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/ftr-screenshot-filename", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-ftr-screenshot-filename/tsconfig.json b/packages/kbn-ftr-screenshot-filename/tsconfig.json new file mode 100644 index 0000000000000..81935b1385550 --- /dev/null +++ b/packages/kbn-ftr-screenshot-filename/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/kbn-journeys/BUILD.bazel b/packages/kbn-journeys/BUILD.bazel new file mode 100644 index 0000000000000..cfadfb4b8b4b7 --- /dev/null +++ b/packages/kbn-journeys/BUILD.bazel @@ -0,0 +1,134 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "kbn-journeys" +PKG_REQUIRE_NAME = "@kbn/journeys" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/mocha", + "@npm//playwright", + "@npm//uuid", + "@npm//axios", + "@npm//callsites", + "@npm//rxjs", + "@npm//elastic-apm-node", + "//packages/kbn-ftr-common-functional-services:npm_module_types", + "//packages/kbn-ftr-screenshot-filename:npm_module_types", + "//packages/kbn-test:npm_module_types", + "//packages/kbn-utils:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-journeys/README.mdx b/packages/kbn-journeys/README.mdx new file mode 100644 index 0000000000000..506b5eb21a9e5 --- /dev/null +++ b/packages/kbn-journeys/README.mdx @@ -0,0 +1,32 @@ +--- +id: kibDevDocsOpsJourneys +slug: /kibana-dev-docs/ops/journeys +title: Journeys +description: A new style of functional test, focused on performance testing for now +tags: ['kibana', 'dev', 'contributor', 'operations', 'performance', 'functional', 'testing'] +--- + +Journeys are a slightly newer take on Functional Tests, currently powered by [playwright](https://playwright.dev/docs). + +A Journey is a single pathway through Kibana and looks something like this: + +```ts +import { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; + +export const journey = new Journey({ + esArchives: [ ... ], + kbnArchives: [ ... ], + scalabilitySetup: { ... }, +}) + .step('Go to Discover Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/discover`)); + await page.waitForSelector(subj('discoverDocTable')); + }) + + .step('Expand the first document', async ({ page }) => { + const expandButtons = page.locator(subj('docTableExpandToggleColumn')); + await expandButtons.first().click(); + await page.locator('text="Expanded document"'); + }); +``` \ No newline at end of file diff --git a/packages/kbn-journeys/index.ts b/packages/kbn-journeys/index.ts new file mode 100644 index 0000000000000..cc4c10c685d1c --- /dev/null +++ b/packages/kbn-journeys/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { JourneyConfig } from './journey/journey_config'; +export type { ScalabilityAction, ScalabilitySetup } from './journey/journey_config'; + +export { Journey } from './journey/journey'; +export type { Step } from './journey/journey'; + +export { JourneyScreenshots } from './journey/journey_screenshots'; diff --git a/packages/kbn-journeys/jest.config.js b/packages/kbn-journeys/jest.config.js new file mode 100644 index 0000000000000..3d4735db3ddf8 --- /dev/null +++ b/packages/kbn-journeys/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-journeys'], +}; diff --git a/packages/kbn-journeys/journey/journey.ts b/packages/kbn-journeys/journey/journey.ts new file mode 100644 index 0000000000000..c399db0ba91c4 --- /dev/null +++ b/packages/kbn-journeys/journey/journey.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { inspect } from 'util'; + +import { Page } from 'playwright'; +import callsites from 'callsites'; +import { ToolingLog } from '@kbn/tooling-log'; +import { FtrConfigProvider } from '@kbn/test'; +import { FtrProviderContext } from '@kbn/ftr-common-functional-services'; + +import { Auth } from '../services/auth'; +import { InputDelays } from '../services/input_delays'; +import { KibanaUrl } from '../services/kibana_url'; + +import { JourneyFtrHarness } from './journey_ftr_harness'; +import { makeFtrConfigProvider } from './journey_ftr_config'; +import { JourneyConfig, JourneyConfigOptions } from './journey_config'; + +export interface BaseStepCtx { + page: Page; + log: ToolingLog; + inputDelays: InputDelays; + kbnUrl: KibanaUrl; +} + +export type AnyStep = Step<{}>; + +export interface Step { + name: string; + index: number; + fn(ctx: BaseStepCtx & CtxExt): Promise; +} + +const CONFIG_PROVIDER_CACHE = new WeakMap, FtrConfigProvider>(); + +export class Journey { + static convertToFtrConfigProvider(journey: Journey) { + const cached = CONFIG_PROVIDER_CACHE.get(journey); + if (cached) { + return cached; + } + + const provider = makeFtrConfigProvider(journey.config, journey.#steps); + CONFIG_PROVIDER_CACHE.set(journey, provider); + return provider; + } + + /** + * Load a journey from a file path + */ + static async load(path: string) { + let m; + try { + m = await import(path); + } catch (error) { + throw new Error(`Unable to load file: ${path}`); + } + + if (!m || !m.journey) { + throw new Error(`[${path}] is not a journey`); + } + + const journey = m.journey; + if (journey instanceof Journey) { + return journey; + } + + const dbg = inspect(journey); + throw new Error(`[${path}] does not export a Journey like it should, received ${dbg}`); + } + + #steps: Array> = []; + + config: JourneyConfig; + + /** + * Create a Journey which should be exported from a file in the + * x-pack/performance/journeys directory. + */ + constructor(opts?: JourneyConfigOptions) { + const path = callsites().at(1)?.getFileName(); + + if (!path) { + throw new Error('unable to determine path of journey config file'); + } + + this.config = new JourneyConfig(path, opts); + } + + /** + * Define a step of this Journey. Steps are only separated from each other + * to aid in reading/debuging the journey and reading it's logging output. + * + * If a journey fails, a failure report will be created with a screenshot + * at the point of failure as well as a screenshot at the end of every + * step. + */ + step(name: string, fn: (ctx: BaseStepCtx & CtxExt) => Promise) { + this.#steps.push({ + name, + index: this.#steps.length, + fn, + }); + + return this; + } + + /** called by FTR to setup tests */ + protected testProvider({ getService }: FtrProviderContext) { + new JourneyFtrHarness( + getService('log'), + getService('config'), + getService('esArchiver'), + getService('kibanaServer'), + new Auth(getService('config'), getService('log'), getService('kibanaServer')), + this.config + ).initMochaSuite(this.#steps); + } +} diff --git a/packages/kbn-journeys/journey/journey_config.ts b/packages/kbn-journeys/journey/journey_config.ts new file mode 100644 index 0000000000000..e23b2a748fbe7 --- /dev/null +++ b/packages/kbn-journeys/journey/journey_config.ts @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; + +import { REPO_ROOT } from '@kbn/utils'; + +import { BaseStepCtx } from './journey'; + +export interface RampConcurrentUsersAction { + action: 'rampConcurrentUsers'; + /** + * Duration strings must be formatted as string that starts with an integer and + * ends with either "m" or "s" for minutes and seconds, respectively + * + * eg: "1m" or "30s" + */ + duration: string; + minUsersCount: number; + maxUsersCount: number; +} + +export interface ConstantConcurrentUsersAction { + action: 'constantConcurrentUsers'; + /** + * Duration strings must be formatted as string that starts with an integer and + * ends with either "m" or "s" for minutes and seconds, respectively + * + * eg: "1m" or "30s" + */ + duration: string; + userCount: number; +} + +export type ScalabilityAction = RampConcurrentUsersAction | ConstantConcurrentUsersAction; + +export interface ScalabilitySetup { + /** + * Duration strings must be formatted as string that starts with an integer and + * ends with either "m" or "s" for minutes and seconds, respectively + * + * eg: "1m" or "30s" + */ + maxDuration: string; + warmup: ScalabilityAction[]; + test: ScalabilityAction[]; +} + +export interface JourneyConfigOptions { + /** + * Set to `true` to skip this journey. should probably be preceded + * by a link to a Github issue where the reasoning for why this was + * skipped and not just deleted is outlined. + */ + skipped?: boolean; + /** + * Scalability configuration used to customize automatically generated + * scalability tests. For now chat with Dima/Operations if you want to + * use this option. + */ + scalabilitySetup?: ScalabilitySetup; + /** + * These labels will be attached to all APM data created when running + * this journey. + */ + extraApmLabels?: Record; + /** + * A list of kbnArchives which will be automatically loaded/unloaded + * for this journey. + */ + kbnArchives?: string[]; + /** + * A list of esArchives which will be automatically loaded/unloaded + * for this journey. + */ + esArchives?: string[]; + /** + * By default the API is used to get a cookie that can be used for all + * navigation requests to Kibana, so that we don't ever see the login + * screen. Set this to `false` to disable this behavior. + */ + skipAutoLogin?: boolean; + /** + * Use this to extend the context provided to each step. This function + * is called with the default context and returns an object that will + * be merged with the default context provided to each step function. + */ + extendContext?: (ctx: BaseStepCtx) => CtxExt; +} + +export class JourneyConfig { + #opts: JourneyConfigOptions; + #path: string; + #name: string; + + constructor(path: string, opts: JourneyConfigOptions = {}) { + this.#path = path; + this.#name = Path.basename(this.#path, Path.extname(this.#path)); + this.#opts = opts; + } + + getEsArchives() { + return this.#opts.esArchives ?? []; + } + + getKbnArchives() { + return this.#opts.kbnArchives ?? []; + } + + isXpack() { + return this.getRepoRelPath().split(Path.sep).at(0) === 'x-pack'; + } + + getExtraApmLabels() { + return this.#opts.extraApmLabels ? { ...this.#opts.extraApmLabels } : {}; + } + + getRepoRelPath() { + return Path.relative(REPO_ROOT, this.getPath()); + } + + getPath() { + return this.#path; + } + + getName() { + return this.#name; + } + + shouldAutoLogin() { + return !this.#opts.skipAutoLogin; + } + + isSkipped() { + return !!this.#opts.skipped; + } + + getScalabilityConfig() { + return this.#opts.scalabilitySetup; + } + + getExtendedStepCtx(ctx: BaseStepCtx): BaseStepCtx & CtxExt { + const ext = this.#opts.extendContext ?? (() => ({} as CtxExt)); + + return { + ...ctx, + ...ext(ctx), + }; + } +} diff --git a/packages/kbn-journeys/journey/journey_ftr_config.ts b/packages/kbn-journeys/journey/journey_ftr_config.ts new file mode 100644 index 0000000000000..392ad69b63ba1 --- /dev/null +++ b/packages/kbn-journeys/journey/journey_ftr_config.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; + +import { v4 as uuidV4 } from 'uuid'; +import { REPO_ROOT } from '@kbn/utils'; +import { FtrConfigProviderContext, FtrConfigProvider } from '@kbn/test'; +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; + +import { AnyStep } from './journey'; +import { JourneyConfig } from './journey_config'; + +// These "secret" values are intentionally written in the source. We would make the APM server accept anonymous traffic if we could +const APM_SERVER_URL = 'https://kibana-ops-e2e-perf.apm.us-central1.gcp.cloud.es.io:443'; +const APM_PUBLIC_TOKEN = 'CTs9y3cvcfq13bQqsB'; + +export function makeFtrConfigProvider( + config: JourneyConfig, + steps: AnyStep[] +): FtrConfigProvider { + return async ({ readConfigFile }: FtrConfigProviderContext) => { + const baseConfig = ( + await readConfigFile( + Path.resolve( + REPO_ROOT, + config.isXpack() + ? 'x-pack/test/functional/config.base.js' + : 'test/functional/config.base.js' + ) + ) + ).getAll(); + + const testBuildId = process.env.BUILDKITE_BUILD_ID ?? `local-${uuidV4()}`; + const testJobId = process.env.BUILDKITE_JOB_ID ?? `local-${uuidV4()}`; + const prId = process.env.GITHUB_PR_NUMBER + ? Number.parseInt(process.env.GITHUB_PR_NUMBER, 10) + : undefined; + + if (Number.isNaN(prId)) { + throw new Error('invalid GITHUB_PR_NUMBER environment variable'); + } + + const telemetryLabels: Record = { + branch: process.env.BUILDKITE_BRANCH, + ciBuildId: process.env.BUILDKITE_BUILD_ID, + ciBuildJobId: process.env.BUILDKITE_JOB_ID, + ciBuildNumber: Number(process.env.BUILDKITE_BUILD_NUMBER) || 0, + gitRev: process.env.BUILDKITE_COMMIT, + isPr: prId !== undefined, + ...(prId !== undefined ? { prId } : {}), + ciBuildName: process.env.BUILDKITE_PIPELINE_SLUG, + journeyName: config.getName(), + }; + + return { + ...baseConfig, + + mochaOpts: { + ...baseConfig.mochaOpts, + bail: true, + }, + + services: commonFunctionalServices, + pageObjects: {}, + + servicesRequiredForTestAnalysis: ['performance', 'journeyConfig'], + + junit: { + reportName: `Journey: ${config.getName()}`, + metadata: { + journeyName: config.getName(), + stepNames: steps.map((s) => s.name), + }, + }, + + kbnTestServer: { + ...baseConfig.kbnTestServer, + // delay shutdown by 15 seconds to ensure that APM can report the data it collects during test execution + delayShutdown: 15_000, + + serverArgs: [ + ...baseConfig.kbnTestServer.serverArgs, + `--telemetry.optIn=${process.env.TEST_PERFORMANCE_PHASE === 'TEST'}`, + `--telemetry.labels=${JSON.stringify(telemetryLabels)}`, + '--csp.strict=false', + '--csp.warnLegacyBrowsers=false', + ], + + env: { + ELASTIC_APM_ACTIVE: process.env.TEST_PERFORMANCE_PHASE ? 'true' : 'false', + ELASTIC_APM_CONTEXT_PROPAGATION_ONLY: 'false', + ELASTIC_APM_ENVIRONMENT: process.env.CI ? 'ci' : 'development', + ELASTIC_APM_TRANSACTION_SAMPLE_RATE: '1.0', + ELASTIC_APM_SERVER_URL: APM_SERVER_URL, + ELASTIC_APM_SECRET_TOKEN: APM_PUBLIC_TOKEN, + // capture request body for both errors and request transactions + // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#capture-body + ELASTIC_APM_CAPTURE_BODY: 'all', + // capture request headers + // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#capture-headers + ELASTIC_APM_CAPTURE_HEADERS: true, + // request body with bigger size will be trimmed. + // 300_000 is the default of the APM server. + // for a body with larger size, we might need to reconfigure the APM server to increase the limit. + // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#long-field-max-length + ELASTIC_APM_LONG_FIELD_MAX_LENGTH: 300_000, + ELASTIC_APM_GLOBAL_LABELS: Object.entries({ + ...config.getExtraApmLabels(), + testJobId, + testBuildId, + journeyName: config.getName(), + ftrConfig: config.getRepoRelPath(), + performancePhase: process.env.TEST_PERFORMANCE_PHASE, + }) + .flatMap(([key, value]) => (value == null ? [] : `${key}=${value}`)) + .join(','), + }, + }, + }; + }; +} diff --git a/packages/kbn-journeys/journey/journey_ftr_harness.ts b/packages/kbn-journeys/journey/journey_ftr_harness.ts new file mode 100644 index 0000000000000..672b14f0e1a85 --- /dev/null +++ b/packages/kbn-journeys/journey/journey_ftr_harness.ts @@ -0,0 +1,410 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Url from 'url'; +import { inspect, format } from 'util'; +import { setTimeout } from 'timers/promises'; + +import * as Rx from 'rxjs'; +import apmNode from 'elastic-apm-node'; +import playwright, { ChromiumBrowser, Page, BrowserContext, CDPSession, Request } from 'playwright'; +import { asyncMap, asyncForEach } from '@kbn/std'; +import { ToolingLog } from '@kbn/tooling-log'; +import { Config } from '@kbn/test'; +import { EsArchiver, KibanaServer } from '@kbn/ftr-common-functional-services'; + +import { Auth } from '../services/auth'; +import { getInputDelays } from '../services/input_delays'; +import { KibanaUrl } from '../services/kibana_url'; + +import type { Step, AnyStep } from './journey'; +import type { JourneyConfig } from './journey_config'; +import { JourneyScreenshots } from './journey_screenshots'; + +export class JourneyFtrHarness { + private readonly screenshots: JourneyScreenshots; + + constructor( + private readonly log: ToolingLog, + private readonly config: Config, + private readonly esArchiver: EsArchiver, + private readonly kibanaServer: KibanaServer, + private readonly auth: Auth, + private readonly journeyConfig: JourneyConfig + ) { + this.screenshots = new JourneyScreenshots(this.journeyConfig.getName()); + } + + private browser: ChromiumBrowser | undefined; + private page: Page | undefined; + private client: CDPSession | undefined; + private context: BrowserContext | undefined; + private currentSpanStack: Array = []; + private currentTransaction: apmNode.Transaction | undefined | null = undefined; + + private pageTeardown$ = new Rx.Subject(); + private telemetryTrackerSubs = new Map(); + + private apm: apmNode.Agent | null = null; + + private async setupApm() { + const kbnTestServerEnv = this.config.get(`kbnTestServer.env`); + + this.apm = apmNode.start({ + serviceName: 'functional test runner', + environment: process.env.CI ? 'ci' : 'development', + active: kbnTestServerEnv.ELASTIC_APM_ACTIVE !== 'false', + serverUrl: kbnTestServerEnv.ELASTIC_APM_SERVER_URL, + secretToken: kbnTestServerEnv.ELASTIC_APM_SECRET_TOKEN, + globalLabels: kbnTestServerEnv.ELASTIC_APM_GLOBAL_LABELS, + transactionSampleRate: kbnTestServerEnv.ELASTIC_APM_TRANSACTION_SAMPLE_RATE, + logger: { + warn: (...args: any[]) => { + this.log.warning('APM WARN', ...args); + }, + info: (...args: any[]) => { + this.log.info('APM INFO', ...args); + }, + fatal: (...args: any[]) => { + this.log.error(format('APM FATAL', ...args)); + }, + error: (...args: any[]) => { + this.log.error(format('APM ERROR', ...args)); + }, + debug: (...args: any[]) => { + this.log.debug('APM DEBUG', ...args); + }, + trace: (...args: any[]) => { + this.log.verbose('APM TRACE', ...args); + }, + }, + }); + + if (this.currentTransaction) { + throw new Error(`Transaction exist, end prev transaction ${this.currentTransaction?.name}`); + } + + this.currentTransaction = this.apm?.startTransaction( + `Journey: ${this.journeyConfig.getName()}`, + 'performance' + ); + } + + private async setupBrowserAndPage() { + const browser = await this.getBrowserInstance(); + this.context = await browser.newContext({ bypassCSP: true }); + + if (this.journeyConfig.shouldAutoLogin()) { + const cookie = await this.auth.login({ username: 'elastic', password: 'changeme' }); + await this.context.addCookies([cookie]); + } + + this.page = await this.context.newPage(); + + if (!process.env.NO_BROWSER_LOG) { + this.page.on('console', this.onConsoleEvent); + } + + await this.sendCDPCommands(this.context, this.page); + + this.trackTelemetryRequests(this.page); + await this.interceptBrowserRequests(this.page); + } + + private async onSetup() { + await Promise.all([ + this.setupApm(), + this.setupBrowserAndPage(), + asyncForEach(this.journeyConfig.getEsArchives(), async (esArchive) => { + await this.esArchiver.load(esArchive); + }), + asyncForEach(this.journeyConfig.getKbnArchives(), async (kbnArchive) => { + await this.kibanaServer.importExport.load(kbnArchive); + }), + ]); + } + + private async tearDownBrowserAndPage() { + if (this.page) { + const telemetryTracker = this.telemetryTrackerSubs.get(this.page); + this.telemetryTrackerSubs.delete(this.page); + + if (telemetryTracker && !telemetryTracker.closed) { + this.log.info(`Waiting for telemetry requests, including starting within next 3 secs`); + this.pageTeardown$.next(this.page); + await new Promise((resolve) => telemetryTracker.add(resolve)); + } + + this.log.info('destroying page'); + await this.client?.detach(); + await this.page.close(); + await this.context?.close(); + } + + if (this.browser) { + this.log.info('closing browser'); + await this.browser.close(); + } + } + + private async teardownApm() { + if (!this.apm) { + return; + } + + if (this.currentTransaction) { + this.currentTransaction.end('Success'); + this.currentTransaction = undefined; + } + + const apmStarted = this.apm.isStarted(); + // @ts-expect-error + const apmActive = apmStarted && this.apm._conf.active; + + if (!apmActive) { + this.log.warning('APM is not active'); + return; + } + + this.log.info('Flushing APM'); + await new Promise((resolve) => this.apm?.flush(() => resolve())); + // wait for the HTTP request that apm.flush() starts, which we + // can't track but hope it is started within 3 seconds, node will stay + // alive for active requests + // https://github.com/elastic/apm-agent-nodejs/issues/2088 + await setTimeout(3000); + } + + private async onTeardown() { + await Promise.all([ + this.tearDownBrowserAndPage(), + this.teardownApm(), + asyncForEach(this.journeyConfig.getEsArchives(), async (esArchive) => { + await this.esArchiver.unload(esArchive); + }), + asyncForEach(this.journeyConfig.getKbnArchives(), async (kbnArchive) => { + await this.kibanaServer.importExport.unload(kbnArchive); + }), + ]); + } + + private async onStepSuccess(step: AnyStep) { + if (!this.page) { + return; + } + + await this.screenshots.addSuccess(step, await this.page.screenshot()); + } + + private async onStepError(step: AnyStep, err: Error) { + if (this.currentTransaction) { + this.currentTransaction.end(`Failure ${err.message}`); + this.currentTransaction = undefined; + } + + if (this.page) { + await this.screenshots.addError(step, await this.page.screenshot()); + } + } + + private async withSpan(name: string, type: string | undefined, block: () => Promise) { + if (!this.currentTransaction) { + return await block(); + } + + const span = this.apm?.startSpan(name, type ?? null, { + childOf: this.currentTransaction, + }); + if (!span) { + return await block(); + } + + try { + this.currentSpanStack.unshift(span); + const result = await block(); + span.setOutcome('success'); + span.end(); + return result; + } catch (error) { + span.setOutcome('failure'); + span.end(); + throw error; + } finally { + if (span !== this.currentSpanStack.shift()) { + // eslint-disable-next-line no-unsafe-finally + throw new Error('span stack mismatch'); + } + } + } + + private getCurrentTraceparent() { + return (this.currentSpanStack.length ? this.currentSpanStack[0] : this.currentTransaction) + ?.traceparent; + } + + private async getBrowserInstance() { + if (this.browser) { + return this.browser; + } + return await this.withSpan('Browser creation', 'setup', async () => { + const headless = !!(process.env.TEST_BROWSER_HEADLESS || process.env.CI); + this.browser = await playwright.chromium.launch({ headless, timeout: 60_000 }); + return this.browser; + }); + } + + private async sendCDPCommands(context: BrowserContext, page: Page) { + const client = await context.newCDPSession(page); + + await client.send('Network.clearBrowserCache'); + await client.send('Network.setCacheDisabled', { cacheDisabled: true }); + await client.send('Network.emulateNetworkConditions', { + latency: 100, + downloadThroughput: 750_000, + uploadThroughput: 750_000, + offline: false, + }); + + return client; + } + + private telemetryTrackerCount = 0; + + private trackTelemetryRequests(page: Page) { + const id = ++this.telemetryTrackerCount; + + const requestFailure$ = Rx.fromEvent(page, 'requestfailed'); + const requestSuccess$ = Rx.fromEvent(page, 'requestfinished'); + const request$ = Rx.fromEvent(page, 'request').pipe( + Rx.takeUntil( + this.pageTeardown$.pipe( + Rx.first((p) => p === page), + Rx.delay(3000) + // If EBT client buffers: + // Rx.mergeMap(async () => { + // await page.waitForFunction(() => { + // // return window.kibana_ebt_client.buffer_size == 0 + // }); + // }) + ) + ), + Rx.mergeMap((request) => { + if (!request.url().includes('telemetry-staging.elastic.co')) { + return Rx.EMPTY; + } + + this.log.debug(`Waiting for telemetry request #${id} to complete`); + return Rx.merge(requestFailure$, requestSuccess$).pipe( + Rx.first((r) => r === request), + Rx.tap({ + complete: () => this.log.debug(`Telemetry request #${id} complete`), + }) + ); + }) + ); + + this.telemetryTrackerSubs.set(page, request$.subscribe()); + } + + private async interceptBrowserRequests(page: Page) { + await page.route('**', async (route, request) => { + const headers = await request.allHeaders(); + const traceparent = this.getCurrentTraceparent(); + if (traceparent && request.isNavigationRequest()) { + await route.continue({ headers: { traceparent, ...headers } }); + } else { + await route.continue(); + } + }); + } + + #_ctx?: Record; + private getCtx() { + if (this.#_ctx) { + return this.#_ctx; + } + + const page = this.page; + + if (!page) { + throw new Error('performance service is not properly initialized'); + } + + this.#_ctx = this.journeyConfig.getExtendedStepCtx({ + page, + log: this.log, + inputDelays: getInputDelays(), + kbnUrl: new KibanaUrl( + new URL( + Url.format({ + protocol: this.config.get('servers.kibana.protocol'), + hostname: this.config.get('servers.kibana.hostname'), + port: this.config.get('servers.kibana.port'), + }) + ) + ), + }); + + return this.#_ctx; + } + + public initMochaSuite(steps: Array>) { + const journeyName = this.journeyConfig.getName(); + + (this.journeyConfig.isSkipped() ? describe.skip : describe)(`Journey[${journeyName}]`, () => { + before(async () => await this.onSetup()); + after(async () => await this.onTeardown()); + + for (const step of steps) { + it(step.name, async () => { + await this.withSpan(`step: ${step.name}`, 'step', async () => { + try { + await step.fn(this.getCtx()); + await this.onStepSuccess(step); + } catch (e) { + const error = new Error(`Step [${step.name}] failed: ${e.message}`); + error.stack = e.stack; + await this.onStepError(step, error); + throw error; // Rethrow error if step fails otherwise it is silently passing + } + }); + }); + } + }); + } + + private onConsoleEvent = async (message: playwright.ConsoleMessage) => { + try { + const { url, lineNumber, columnNumber } = message.location(); + const location = `${url}:${lineNumber}:${columnNumber}`; + + const args = await asyncMap(message.args(), (handle) => handle.jsonValue()); + const text = args.length + ? args.map((arg) => (typeof arg === 'string' ? arg : inspect(arg, false, null))).join(' ') + : message.text(); + + if ( + url.includes('kbn-ui-shared-deps-npm.dll.js') && + text.includes('moment construction falls') + ) { + // ignore errors from moment about constructing dates with invalid formats + return; + } + + const type = message.type(); + const method = type === 'debug' ? type : type === 'warning' ? 'error' : 'info'; + const name = type === 'warning' ? 'error' : 'log'; + this.log[method](`[console.${name}] @ ${location}:\n${text}`); + } catch (error) { + const dbg = inspect(message); + this.log.error( + `Error interpreting browser console.log:\nerror:${error.message}\nmessage:\n${dbg}` + ); + } + }; +} diff --git a/packages/kbn-journeys/journey/journey_screenshots.ts b/packages/kbn-journeys/journey/journey_screenshots.ts new file mode 100644 index 0000000000000..8cd36444ef7ee --- /dev/null +++ b/packages/kbn-journeys/journey/journey_screenshots.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 Path from 'path'; +import Fsp from 'fs/promises'; + +import * as Rx from 'rxjs'; +import { REPO_ROOT } from '@kbn/utils'; +import { FtrScreenshotFilename } from '@kbn/ftr-screenshot-filename'; + +import type { AnyStep } from './journey'; + +interface StepShot { + type: 'success' | 'failure'; + title: string; + filename: string; +} + +interface Manifest { + steps: StepShot[]; +} + +const isObj = (v: unknown): v is Record => typeof v === 'object' && v !== null; +const isString = (v: unknown): v is string => typeof v === 'string'; +const isStepShot = (v: unknown): v is StepShot => + isObj(v) && + (v.type === 'success' || v.type === 'failure') && + isString(v.title) && + isString(v.filename); + +const write = async (path: string, content: string | Buffer) => { + await Fsp.mkdir(Path.dirname(path), { recursive: true }); + await Fsp.writeFile(path, content); +}; + +export class JourneyScreenshots { + static async load(journeyName: string) { + const screenshots = new JourneyScreenshots(journeyName); + + const json = await Fsp.readFile(screenshots.#manifestPath, 'utf8'); + const manifest = JSON.parse(json); + + if (!isObj(manifest)) { + throw new Error('invalid manifest, json parsed but not to an object'); + } + + const { steps } = manifest; + + if (!Array.isArray(steps) || !steps.every(isStepShot)) { + throw new Error('invalid manifest, steps must be an array of StepShot objects'); + } + + screenshots.#manifest = { steps }; + return screenshots; + } + + readonly #dir: string; + readonly #manifestPath: string; + + #manifest: Manifest = { + steps: [], + }; + + constructor(journeyName: string) { + this.#dir = Path.resolve(REPO_ROOT, 'data/journey_screenshots', journeyName); + this.#manifestPath = Path.resolve(this.#dir, 'manifest.json'); + } + + readonly #isLocked = new Rx.BehaviorSubject(false); + async lock(fn: () => Promise) { + if (this.#isLocked.getValue()) { + do { + await Rx.firstValueFrom(this.#isLocked.pipe(Rx.skip(1))); + } while (this.#isLocked.getValue()); + } + + try { + this.#isLocked.next(true); + await fn(); + } finally { + this.#isLocked.next(false); + } + } + + async addError(step: AnyStep, screenshot: Buffer) { + await this.lock(async () => { + const filename = FtrScreenshotFilename.create(`${step.index}-${step.name}-failure`); + this.#manifest.steps.push({ + type: 'failure', + title: `Step #${step.index + 1}: ${step.name} - FAILED`, + filename, + }); + + await Promise.all([ + write(Path.resolve(this.#dir, 'manifest.json'), JSON.stringify(this.#manifest)), + write(Path.resolve(this.#dir, filename), screenshot), + ]); + }); + } + + async addSuccess(step: AnyStep, screenshot: Buffer) { + await this.lock(async () => { + const filename = FtrScreenshotFilename.create(`${step.index}-${step.name}`); + this.#manifest.steps.push({ + type: 'success', + title: `Step #${step.index + 1}: ${step.name} - DONE`, + filename, + }); + + await Promise.all([ + write(Path.resolve(this.#dir, 'manifest.json'), JSON.stringify(this.#manifest)), + write(Path.resolve(this.#dir, filename), screenshot), + ]); + }); + } + + get() { + return this.#manifest.steps.map((stepShot) => ({ + ...stepShot, + path: Path.resolve(this.#dir, stepShot.filename), + })); + } +} diff --git a/packages/kbn-journeys/kibana.jsonc b/packages/kbn-journeys/kibana.jsonc new file mode 100644 index 0000000000000..ab8a15547c158 --- /dev/null +++ b/packages/kbn-journeys/kibana.jsonc @@ -0,0 +1,8 @@ +{ + "type": "shared-common", + "id": "@kbn/journeys", + "owner": "@elastic/kibana-operations", + "devOnly": true, + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/kbn-journeys/package.json b/packages/kbn-journeys/package.json new file mode 100644 index 0000000000000..06920a5ebd241 --- /dev/null +++ b/packages/kbn-journeys/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/journeys", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-journeys/services/auth.ts b/packages/kbn-journeys/services/auth.ts new file mode 100644 index 0000000000000..b8c68a9fbb09c --- /dev/null +++ b/packages/kbn-journeys/services/auth.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 Url from 'url'; +import { format } from 'util'; + +import axios, { AxiosResponse } from 'axios'; +import { ToolingLog } from '@kbn/tooling-log'; +import { Config } from '@kbn/test'; +import { KibanaServer } from '@kbn/ftr-common-functional-services'; + +export interface Credentials { + username: string; + password: string; +} + +function extractCookieValue(authResponse: AxiosResponse) { + return authResponse.headers['set-cookie']?.[0].toString().split(';')[0].split('sid=')[1] ?? ''; +} +export class Auth { + constructor( + private readonly config: Config, + private readonly log: ToolingLog, + private readonly kibanaServer: KibanaServer + ) {} + + public async login({ username, password }: Credentials) { + const baseUrl = new URL( + Url.format({ + protocol: this.config.get('servers.kibana.protocol'), + hostname: this.config.get('servers.kibana.hostname'), + port: this.config.get('servers.kibana.port'), + }) + ); + + const loginUrl = new URL('/internal/security/login', baseUrl); + const provider = baseUrl.hostname === 'localhost' ? 'basic' : 'cloud-basic'; + + this.log.info('fetching auth cookie from', loginUrl.href); + const authResponse = await axios.request({ + url: loginUrl.href, + method: 'post', + data: { + providerType: 'basic', + providerName: provider, + currentURL: new URL('/login?next=%2F', baseUrl).href, + params: { username, password }, + }, + headers: { + 'content-type': 'application/json', + 'kbn-version': await this.kibanaServer.version.get(), + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + }, + validateStatus: () => true, + maxRedirects: 0, + }); + + const cookie = extractCookieValue(authResponse); + if (cookie) { + this.log.info('captured auth cookie'); + } else { + this.log.error( + format('unable to determine auth cookie from response', { + status: `${authResponse.status} ${authResponse.statusText}`, + body: authResponse.data, + headers: authResponse.headers, + }) + ); + + throw new Error(`failed to determine auth cookie`); + } + + return { + name: 'sid', + value: cookie, + url: baseUrl.href, + }; + } +} diff --git a/x-pack/test/performance/services/input_delays.ts b/packages/kbn-journeys/services/input_delays.ts similarity index 69% rename from x-pack/test/performance/services/input_delays.ts rename to packages/kbn-journeys/services/input_delays.ts index 483974cb9802d..5f66b26f0f36d 100644 --- a/x-pack/test/performance/services/input_delays.ts +++ b/packages/kbn-journeys/services/input_delays.ts @@ -1,10 +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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -interface InputDelays { + +export interface InputDelays { TYPING: number; MOUSE_CLICK: number; } @@ -20,7 +22,7 @@ const PROFILES: Record = { }, }; -export function InputDelaysProvider(): InputDelays { +export function getInputDelays(): InputDelays { const profile = PROFILES[process.env.INPUT_DELAY_PROFILE ?? 'user']; if (!profile) { diff --git a/packages/kbn-journeys/services/kibana_url.ts b/packages/kbn-journeys/services/kibana_url.ts new file mode 100644 index 0000000000000..d9c54ccfe3c37 --- /dev/null +++ b/packages/kbn-journeys/services/kibana_url.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface PathOptions { + /** + * Query string parameters + */ + params?: Record; + /** + * The hash value of the URL + */ + hash?: string; +} + +export class KibanaUrl { + #baseUrl: URL; + + constructor(baseUrl: URL) { + this.#baseUrl = baseUrl; + } + + /** + * Get an absolute URL based on Kibana's URL + * @param rel relative url, resolved relative to Kibana's url + * @param options optional modifications to apply to the URL + */ + get(rel?: string, options?: PathOptions) { + const url = new URL(rel ?? '/', this.#baseUrl); + + if (options?.params) { + for (const [key, value] of Object.entries(options.params)) { + url.searchParams.set(key, value); + } + } + + if (options?.hash !== undefined) { + url.hash = options.hash; + } + + return url.href; + } + + /** + * Get the URL for an app + * @param appName name of the app to get the URL for + * @param options optional modifications to apply to the URL + */ + app(appName: string, options?: PathOptions) { + return this.get(`/app/${appName}`, options); + } + + toString() { + return this.#baseUrl.href; + } +} diff --git a/packages/kbn-journeys/tsconfig.json b/packages/kbn-journeys/tsconfig.json new file mode 100644 index 0000000000000..f4d18db9ffafa --- /dev/null +++ b/packages/kbn-journeys/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "mocha", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 02687b4a1e624..b8de0b6daa3b5 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -10,6 +10,7 @@ pageLoadAssetSize: cases: 144442 charts: 55000 cloud: 21076 + cloudExperiments: 59358 cloudSecurityPosture: 19109 console: 46091 controls: 40000 @@ -31,7 +32,7 @@ pageLoadAssetSize: embeddableEnhanced: 22107 enterpriseSearch: 35741 esUiShared: 326654 - eventAnnotation: 19500 + eventAnnotation: 20500 expressionError: 22127 expressionGauge: 25000 expressionHeatmap: 27505 diff --git a/packages/kbn-performance-testing-dataset-extractor/BUILD.bazel b/packages/kbn-performance-testing-dataset-extractor/BUILD.bazel index 479e494bf3b54..53782e9cfbd08 100644 --- a/packages/kbn-performance-testing-dataset-extractor/BUILD.bazel +++ b/packages/kbn-performance-testing-dataset-extractor/BUILD.bazel @@ -66,8 +66,8 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-dev-cli-errors:npm_module_types", "//packages/kbn-dev-cli-runner:npm_module_types", - "//packages/kbn-test:npm_module_types", "//packages/kbn-tooling-log:npm_module_types", + "//packages/kbn-journeys:npm_module_types", "@npm//@elastic/elasticsearch", "@npm//@types/node", "@npm//@types/jest", diff --git a/packages/kbn-performance-testing-dataset-extractor/src/cli.ts b/packages/kbn-performance-testing-dataset-extractor/src/cli.ts index 435f87bcd5818..f2e88addd63fe 100644 --- a/packages/kbn-performance-testing-dataset-extractor/src/cli.ts +++ b/packages/kbn-performance-testing-dataset-extractor/src/cli.ts @@ -12,16 +12,13 @@ * *************************************************************/ +import path from 'path'; + import { run } from '@kbn/dev-cli-runner'; import { createFlagError } from '@kbn/dev-cli-errors'; -import { EsVersion, readConfigFile } from '@kbn/test'; -import path from 'path'; -import { extractor } from './extractor'; -import { ScalabilitySetup, TestData } from './types'; +import { Journey } from '@kbn/journeys'; -interface Vars { - [key: string]: string; -} +import { extractor } from './extractor'; export async function runExtractor() { run( @@ -50,63 +47,46 @@ export async function runExtractor() { throw createFlagError('--es-password must be defined'); } + const withoutStaticResources = !!flags['without-static-resources'] || false; + const buildId = flags.buildId; + if (buildId && typeof buildId !== 'string') { + throw createFlagError('--buildId must be a string'); + } + if (!buildId) { + throw createFlagError('--buildId must be defined'); + } + const configPath = flags.config; if (typeof configPath !== 'string') { throw createFlagError('--config must be a string'); } - const config = await readConfigFile(log, EsVersion.getDefault(), path.resolve(configPath)); - - const scalabilitySetup: ScalabilitySetup = config.get('scalabilitySetup'); + const journey = await Journey.load(path.resolve(configPath)); + const scalabilitySetup = journey.config.getScalabilityConfig(); if (!scalabilitySetup) { log.warning( `'scalabilitySetup' is not defined in config file, output file for Kibana scalability run won't be generated` ); } - const testData: TestData = config.get('testData'); - - const env = config.get(`kbnTestServer.env`); - if ( - typeof env !== 'object' || - typeof env.ELASTIC_APM_GLOBAL_LABELS !== 'string' || - !env.ELASTIC_APM_GLOBAL_LABELS.includes('journeyName=') - ) { - log.error( - `'journeyName' must be defined in config file: - - env: { - ...config.kbnTestServer.env, - ELASTIC_APM_GLOBAL_LABELS: Object.entries({ - journeyName: , - }) - },` - ); - return; - } - - const envVars: Vars = env.ELASTIC_APM_GLOBAL_LABELS.split(',').reduce( - (acc: Vars, pair: string) => { - const [key, value] = pair.split('='); - return { ...acc, [key]: value }; - }, - {} - ); - const journeyName = envVars.journeyName; - - const buildId = flags.buildId; - if (buildId && typeof buildId !== 'string') { - throw createFlagError('--buildId must be a string'); - } - if (!buildId) { - throw createFlagError('--buildId must be defined'); - } - - const withoutStaticResources = !!flags['without-static-resources'] || false; + const testData = { + esArchives: journey.config.getEsArchives(), + kbnArchives: journey.config.getKbnArchives(), + }; return extractor({ - param: { journeyName, scalabilitySetup, testData, buildId, withoutStaticResources }, - client: { baseURL, username, password }, + param: { + journeyName: journey.config.getName(), + scalabilitySetup, + testData, + buildId, + withoutStaticResources, + }, + client: { + baseURL, + username, + password, + }, log, }); }, diff --git a/packages/kbn-performance-testing-dataset-extractor/src/types.ts b/packages/kbn-performance-testing-dataset-extractor/src/types.ts index 69df8a5fd490c..3b7eb1a356adf 100644 --- a/packages/kbn-performance-testing-dataset-extractor/src/types.ts +++ b/packages/kbn-performance-testing-dataset-extractor/src/types.ts @@ -7,6 +7,7 @@ */ import { ToolingLog } from '@kbn/tooling-log'; +import { ScalabilitySetup } from '@kbn/journeys'; export interface Request { transactionId: string; @@ -31,19 +32,6 @@ export interface Stream { requests: T[]; } -export interface InjectionStep { - action: string; - minUsersCount?: number; - maxUsersCount: number; - duration: string; -} - -export interface ScalabilitySetup { - warmup: InjectionStep[]; - test: InjectionStep[]; - maxDuration: string; -} - export interface TestData { kbnArchives?: string[]; esArchives?: string[]; @@ -52,7 +40,7 @@ export interface TestData { export interface CLIParams { param: { journeyName: string; - scalabilitySetup: ScalabilitySetup; + scalabilitySetup?: ScalabilitySetup; testData: TestData; buildId: string; withoutStaticResources: boolean; diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel index a51766a8f6c63..1deca3a0f6d07 100644 --- a/packages/kbn-test/BUILD.bazel +++ b/packages/kbn-test/BUILD.bazel @@ -99,6 +99,7 @@ TYPES_DEPS = [ "//packages/kbn-tooling-log:npm_module_types", "//packages/kbn-bazel-packages:npm_module_types", "//packages/kbn-get-repo-files:npm_module_types", + "//packages/kbn-ftr-screenshot-filename:npm_module_types", "@npm//@elastic/elasticsearch", "@npm//@jest/console", "@npm//@jest/reporters", @@ -116,6 +117,7 @@ TYPES_DEPS = [ "@npm//jest-snapshot", "@npm//redux", "@npm//rxjs", + "@npm//playwright", "@npm//xmlbuilder", "@npm//@types/archiver", "@npm//@types/chance", diff --git a/packages/kbn-test/index.ts b/packages/kbn-test/index.ts index befdf3f3d3a9b..8d1b3cefde463 100644 --- a/packages/kbn-test/index.ts +++ b/packages/kbn-test/index.ts @@ -6,23 +6,13 @@ * Side Public License, v 1. */ -// @internal -import { - runTestsCli, - processRunTestsCliOptions, - startServersCli, - processStartServersCliOptions, - // @ts-ignore not typed yet -} from './src/functional_tests/cli'; - export { KbnClientRequesterError } from './src/kbn_client/kbn_client_requester_error'; // @internal -export { runTestsCli, processRunTestsCliOptions, startServersCli, processStartServersCliOptions }; +export { startServersCli, startServers } from './src/functional_tests/start_servers'; -// @ts-ignore not typed yet // @internal -export { runTests, startServers } from './src/functional_tests/tasks'; +export { runTestsCli, runTests } from './src/functional_tests/run_tests'; export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args'; @@ -48,15 +38,9 @@ export { systemIndicesSuperuser, } from './src/kbn'; -export { readConfigFile } from './src/functional_test_runner/lib/config/read_config_file'; - -export { runFtrCli } from './src/functional_test_runner/cli'; - // @internal export { setupJUnitReportGeneration, escapeCdata } from './src/mocha'; -export { runFailedTestsReporterCli } from './src/failed_tests_reporter'; - export { CI_PARALLEL_PROCESS_PREFIX } from './src/ci_parallel_process_prefix'; export * from './src/functional_test_runner'; diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index 35cef7b672a3d..3cb0ce86602cd 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -127,7 +127,7 @@ module.exports = { transformIgnorePatterns: [ // ignore all node_modules except monaco-editor and react-monaco-editor 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(?![\\/\\\\](monaco-editor|react-monaco-editor|d3-interpolate|d3-color))[/\\\\].+\\.js$', + '[/\\\\]node_modules(?![\\/\\\\](byte-size|monaco-editor|react-monaco-editor|d3-interpolate|d3-color))[/\\\\].+\\.js$', 'packages/kbn-pm/dist/index.js', ], diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts b/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts deleted file mode 100644 index 9336c40d35bdb..0000000000000 --- a/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { createHash } from 'crypto'; -import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs'; -import { join, basename, resolve } from 'path'; - -import { ToolingLog } from '@kbn/tooling-log'; -import { REPO_ROOT } from '@kbn/utils'; -import { escape } from 'he'; - -import { BuildkiteMetadata } from './buildkite_metadata'; -import { TestFailure } from './get_failures'; - -const findScreenshots = (dirPath: string, allScreenshots: string[] = []) => { - const files = readdirSync(dirPath); - - for (const file of files) { - if (statSync(join(dirPath, file)).isDirectory()) { - if (file.match(/node_modules/)) { - continue; - } - - allScreenshots = findScreenshots(join(dirPath, file), allScreenshots); - } else { - const fullPath = join(dirPath, file); - if (fullPath.match(/screenshots\/failure\/.+\.png$/)) { - allScreenshots.push(fullPath); - } - } - } - - return allScreenshots; -}; - -export function reportFailuresToFile( - log: ToolingLog, - failures: TestFailure[], - bkMeta: BuildkiteMetadata -) { - if (!failures?.length) { - return; - } - - let screenshots: string[]; - try { - screenshots = [ - ...findScreenshots(join(REPO_ROOT, 'test', 'functional')), - ...findScreenshots(join(REPO_ROOT, 'x-pack', 'test', 'functional')), - ]; - } catch (e) { - log.error(e as Error); - screenshots = []; - } - - const screenshotsByName: Record = {}; - for (const screenshot of screenshots) { - const [name] = basename(screenshot).split('.'); - screenshotsByName[name] = screenshot; - } - - // Jest could, in theory, fail 1000s of tests and write 1000s of failures - // So let's just write files for the first 20 - for (const failure of failures.slice(0, 20)) { - const hash = createHash('md5').update(failure.name).digest('hex'); - const filenameBase = `${ - process.env.BUILDKITE_JOB_ID ? process.env.BUILDKITE_JOB_ID + '_' : '' - }${hash}`; - const dir = join('target', 'test_failures'); - - const failureLog = [ - ['Test:', '-----', failure.classname, failure.name, ''], - ['Failure:', '--------', failure.failure], - failure['system-out'] ? ['', 'Standard Out:', '-------------', failure['system-out']] : [], - ] - .flat() - .join('\n'); - - const failureJSON = JSON.stringify( - { - ...failure, - hash, - buildId: bkMeta.buildId, - jobId: bkMeta.jobId, - url: bkMeta.url, - jobUrl: bkMeta.jobUrl, - jobName: bkMeta.jobName, - }, - null, - 2 - ); - - let screenshot = ''; - const truncatedName = failure.name.replace(/([^ a-zA-Z0-9-]+)/g, '_').slice(0, 80); - const failureNameHash = createHash('sha256').update(failure.name).digest('hex'); - const screenshotName = `${truncatedName}-${failureNameHash}`; - - if (screenshotsByName[screenshotName]) { - try { - screenshot = readFileSync(screenshotsByName[screenshotName]).toString('base64'); - } catch (e) { - log.error(e as Error); - } - } - - const screenshotHtml = screenshot - ? `` - : ''; - - const failureHTML = readFileSync( - resolve( - REPO_ROOT, - 'packages/kbn-test/src/failed_tests_reporter/report_failures_to_file_html_template.html' - ) - ) - .toString() - .replace('$TITLE', escape(failure.name)) - .replace( - '$MAIN', - ` - ${failure.classname - .split('.') - .map((part) => `
${escape(part.replace('·', '.'))}
`) - .join('')} -
-

${escape(failure.name)}

-

- - Failures in tracked branches: ${ - failure.failureCount || 0 - } - ${ - failure.githubIssue - ? `
${escape( - failure.githubIssue - )}` - : '' - } -
-

- ${ - bkMeta.jobUrl - ? `

- - Buildkite Job
- ${escape(bkMeta.jobUrl)} -
-

` - : '' - } -
${escape(failure.failure)}
- ${screenshotHtml} -
${escape(failure['system-out'] || '')}
- ` - ); - - mkdirSync(dir, { recursive: true }); - writeFileSync(join(dir, `${filenameBase}.log`), failureLog, 'utf8'); - writeFileSync(join(dir, `${filenameBase}.html`), failureHTML, 'utf8'); - writeFileSync(join(dir, `${filenameBase}.json`), failureJSON, 'utf8'); - } -} diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts deleted file mode 100644 index 5702372aab7be..0000000000000 --- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; - -import { REPO_ROOT } from '@kbn/utils'; -import { run } from '@kbn/dev-cli-runner'; -import { createFailError, createFlagError } from '@kbn/dev-cli-errors'; -import { CiStatsReporter } from '@kbn/ci-stats-reporter'; -import globby from 'globby'; -import normalize from 'normalize-path'; - -import { getFailures } from './get_failures'; -import { GithubApi } from './github_api'; -import { updateFailureIssue, createFailureIssue } from './report_failure'; -import { readTestReport } from './test_report'; -import { addMessagesToReport } from './add_messages_to_report'; -import { getReportMessageIter } from './report_metadata'; -import { reportFailuresToEs } from './report_failures_to_es'; -import { reportFailuresToFile } from './report_failures_to_file'; -import { getBuildkiteMetadata } from './buildkite_metadata'; -import { ExistingFailedTestIssues } from './existing_failed_test_issues'; - -const DEFAULT_PATTERNS = [Path.resolve(REPO_ROOT, 'target/junit/**/*.xml')]; -const DISABLE_MISSING_TEST_REPORT_ERRORS = - process.env.DISABLE_MISSING_TEST_REPORT_ERRORS === 'true'; - -export function runFailedTestsReporterCli() { - run( - async ({ log, flags }) => { - const indexInEs = flags['index-errors']; - - let updateGithub = flags['github-update']; - if (updateGithub && !process.env.GITHUB_TOKEN) { - throw createFailError( - 'GITHUB_TOKEN environment variable must be set, otherwise use --no-github-update flag' - ); - } - - let branch: string = ''; - if (updateGithub) { - let isPr = false; - - if (process.env.BUILDKITE === 'true') { - branch = process.env.BUILDKITE_BRANCH || ''; - isPr = process.env.BUILDKITE_PULL_REQUEST === 'true'; - updateGithub = process.env.REPORT_FAILED_TESTS_TO_GITHUB === 'true'; - } else { - // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others - const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); - branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH || ''; - isPr = !!process.env.ghprbPullId; - - const isMainOrVersion = branch === 'main' || branch.match(/^\d+\.(x|\d+)$/); - if (!isMainOrVersion || isPr) { - log.info('Failure issues only created on main/version branch jobs'); - updateGithub = false; - } - } - - if (!branch) { - throw createFailError( - 'Unable to determine originating branch from job name or other environment variables' - ); - } - } - - const githubApi = new GithubApi({ - log, - token: process.env.GITHUB_TOKEN, - dryRun: !updateGithub, - }); - - const bkMeta = getBuildkiteMetadata(); - - try { - const buildUrl = flags['build-url'] || (updateGithub ? '' : 'http://buildUrl'); - if (typeof buildUrl !== 'string' || !buildUrl) { - throw createFlagError('Missing --build-url or process.env.BUILD_URL'); - } - - const patterns = (flags._.length ? flags._ : DEFAULT_PATTERNS).map((p) => - normalize(Path.resolve(p)) - ); - log.info('Searching for reports at', patterns); - const reportPaths = await globby(patterns, { - absolute: true, - }); - - if (!reportPaths.length && DISABLE_MISSING_TEST_REPORT_ERRORS) { - // it is fine for code coverage to not have test results - return; - } - - if (!reportPaths.length) { - throw createFailError(`Unable to find any junit reports with patterns [${patterns}]`); - } - - log.info('found', reportPaths.length, 'junit reports', reportPaths); - - const existingIssues = new ExistingFailedTestIssues(log); - for (const reportPath of reportPaths) { - const report = await readTestReport(reportPath); - const messages = Array.from(getReportMessageIter(report)); - const failures = getFailures(report); - - await existingIssues.loadForFailures(failures); - - if (indexInEs) { - await reportFailuresToEs(log, failures); - } - - for (const failure of failures) { - const pushMessage = (msg: string) => { - messages.push({ - classname: failure.classname, - name: failure.name, - message: msg, - }); - }; - - if (failure.likelyIrrelevant) { - pushMessage( - 'Failure is likely irrelevant' + - (updateGithub ? ', so an issue was not created or updated' : '') - ); - continue; - } - - const existingIssue = existingIssues.getForFailure(failure); - if (existingIssue) { - const { newBody, newCount } = await updateFailureIssue( - buildUrl, - existingIssue, - githubApi, - branch - ); - const url = existingIssue.github.htmlUrl; - existingIssue.github.body = newBody; - failure.githubIssue = url; - failure.failureCount = updateGithub ? newCount : newCount - 1; - pushMessage(`Test has failed ${newCount - 1} times on tracked branches: ${url}`); - if (updateGithub) { - pushMessage(`Updated existing issue: ${url} (fail count: ${newCount})`); - } - continue; - } - - const newIssue = await createFailureIssue(buildUrl, failure, githubApi, branch); - existingIssues.addNewlyCreated(failure, newIssue); - pushMessage('Test has not failed recently on tracked branches'); - if (updateGithub) { - pushMessage(`Created new issue: ${newIssue.html_url}`); - failure.githubIssue = newIssue.html_url; - } - failure.failureCount = updateGithub ? 1 : 0; - } - - // mutates report to include messages and writes updated report to disk - await addMessagesToReport({ - report, - messages, - log, - reportPath, - dryRun: !flags['report-update'], - }); - - reportFailuresToFile(log, failures, bkMeta); - } - } finally { - await CiStatsReporter.fromEnv(log).metrics([ - { - group: 'github api request count', - id: `failed test reporter`, - value: githubApi.getRequestCount(), - meta: Object.fromEntries( - Object.entries(bkMeta).map( - ([k, v]) => [`buildkite${k[0].toUpperCase()}${k.slice(1)}`, v] as const - ) - ), - }, - ]); - } - }, - { - description: `a cli that opens issues or updates existing issues based on junit reports`, - flags: { - boolean: ['github-update', 'report-update'], - string: ['build-url'], - default: { - 'github-update': true, - 'report-update': true, - 'index-errors': true, - 'build-url': process.env.BUILD_URL, - }, - help: ` - --no-github-update Execute the CLI without writing to Github - --no-report-update Execute the CLI without writing to the JUnit reports - --no-index-errors Execute the CLI without indexing failures into Elasticsearch - --build-url URL of the failed build, defaults to process.env.BUILD_URL - `, - }, - } - ); -} diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts index de20c93c39995..dfd1edc1d8fc4 100644 --- a/packages/kbn-test/src/functional_test_runner/cli.ts +++ b/packages/kbn-test/src/functional_test_runner/cli.ts @@ -9,26 +9,15 @@ import Path from 'path'; import { inspect } from 'util'; -import { run, Flags } from '@kbn/dev-cli-runner'; +import { run } from '@kbn/dev-cli-runner'; import { createFlagError } from '@kbn/dev-cli-errors'; import { ToolingLog } from '@kbn/tooling-log'; import { getTimeReporter } from '@kbn/ci-stats-reporter'; import exitHook from 'exit-hook'; +import { readConfigFile, EsVersion } from './lib'; import { FunctionalTestRunner } from './functional_test_runner'; -const makeAbsolutePath = (v: string) => Path.resolve(process.cwd(), v); -const toArray = (v: string | string[]) => ([] as string[]).concat(v || []); -const parseInstallDir = (flags: Flags) => { - const flag = flags['kibana-install-dir']; - - if (typeof flag !== 'string' && flag !== undefined) { - throw createFlagError('--kibana-install-dir must be a string or not defined'); - } - - return flag ? makeAbsolutePath(flag) : undefined; -}; - export function runFtrCli() { const runStartTime = Date.now(); const toolingLog = new ToolingLog({ @@ -37,52 +26,49 @@ export function runFtrCli() { }); const reportTime = getTimeReporter(toolingLog, 'scripts/functional_test_runner'); run( - async ({ flags, log }) => { - const esVersion = flags['es-version'] || undefined; // convert "" to undefined - if (esVersion !== undefined && typeof esVersion !== 'string') { - throw createFlagError('expected --es-version to be a string'); + async ({ flagsReader, log }) => { + const esVersionInput = flagsReader.string('es-version'); + + const configPaths = [ + ...(flagsReader.arrayOfStrings('config') ?? []), + ...(flagsReader.arrayOfStrings('journey') ?? []), + ].map((rel) => Path.resolve(rel)); + if (configPaths.length !== 1) { + throw createFlagError(`Expected there to be exactly one --config/--journey flag`); } - const configRel = flags.config; - if (typeof configRel !== 'string' || !configRel) { - throw createFlagError('--config is required'); - } - const configPath = makeAbsolutePath(configRel); - - const functionalTestRunner = new FunctionalTestRunner( - log, - configPath, - { - mochaOpts: { - bail: flags.bail, - dryRun: flags['dry-run'], - grep: flags.grep || undefined, - invert: flags.invert, - }, - kbnTestServer: { - installDir: parseInstallDir(flags), - }, - suiteFiles: { - include: toArray(flags.include as string | string[]).map(makeAbsolutePath), - exclude: toArray(flags.exclude as string | string[]).map(makeAbsolutePath), - }, - suiteTags: { - include: toArray(flags['include-tag'] as string | string[]), - exclude: toArray(flags['exclude-tag'] as string | string[]), - }, - updateBaselines: flags.updateBaselines || flags.u, - updateSnapshots: flags.updateSnapshots || flags.u, + const esVersion = esVersionInput ? new EsVersion(esVersionInput) : EsVersion.getDefault(); + const settingOverrides = { + mochaOpts: { + bail: flagsReader.boolean('bail'), + dryRun: flagsReader.boolean('dry-run'), + grep: flagsReader.string('grep'), + invert: flagsReader.boolean('invert'), }, - esVersion - ); + kbnTestServer: { + installDir: flagsReader.path('kibana-install-dir'), + }, + suiteFiles: { + include: flagsReader.arrayOfPaths('include') ?? [], + exclude: flagsReader.arrayOfPaths('exclude') ?? [], + }, + suiteTags: { + include: flagsReader.arrayOfStrings('include-tag') ?? [], + exclude: flagsReader.arrayOfStrings('exclude-tag') ?? [], + }, + updateBaselines: flagsReader.boolean('updateBaselines') || flagsReader.boolean('u'), + updateSnapshots: flagsReader.boolean('updateSnapshots') || flagsReader.boolean('u'), + }; + + const config = await readConfigFile(log, esVersion, configPaths[0], settingOverrides); - await functionalTestRunner.readConfigFile(); + const functionalTestRunner = new FunctionalTestRunner(log, config, esVersion); - if (flags.throttle) { + if (flagsReader.boolean('throttle')) { process.env.TEST_THROTTLE_NETWORK = '1'; } - if (flags.headless) { + if (flagsReader.boolean('headless')) { process.env.TEST_BROWSER_HEADLESS = '1'; } @@ -95,7 +81,7 @@ export function runFtrCli() { await reportTime(runStartTime, 'total', { success: false, err: err.message, - ...flags, + ...Object.fromEntries(flagsReader.getUsed().entries()), }); log.indent(-log.getIndent()); log.error(err); @@ -103,7 +89,7 @@ export function runFtrCli() { } else { await reportTime(runStartTime, 'total', { success: true, - ...flags, + ...Object.fromEntries(flagsReader.getUsed().entries()), }); } @@ -118,7 +104,7 @@ export function runFtrCli() { exitHook(teardown); try { - if (flags['test-stats']) { + if (flagsReader.boolean('test-stats')) { process.stderr.write( JSON.stringify(await functionalTestRunner.getTestStats(), null, 2) + '\n' ); @@ -139,6 +125,7 @@ export function runFtrCli() { flags: { string: [ 'config', + 'journey', 'grep', 'include', 'exclude', @@ -159,7 +146,8 @@ export function runFtrCli() { 'dry-run', ], help: ` - --config=path path to a config file + --config=path path to a config file (either this or --journey is required) + --journey=path path to a journey file (either this or --config is required) --bail stop tests after the first failure --grep pattern used to select which tests to run --invert invert grep to exclude tests diff --git a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.ts b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.ts index 506b6f139f736..17e9663e33883 100644 --- a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.ts +++ b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.ts @@ -15,6 +15,7 @@ import { EventEmitter } from 'events'; export interface Suite { + currentTest?: Test; suites: Suite[]; tests: Test[]; title: string; diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts index 4e549e960dc26..11f99abfa6fbf 100644 --- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts +++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts @@ -14,7 +14,6 @@ import { REPO_ROOT } from '@kbn/utils'; import { Suite, Test } from './fake_mocha_types'; import { Lifecycle, - readConfigFile, ProviderCollection, Providers, readProviderSpec, @@ -32,8 +31,7 @@ export class FunctionalTestRunner { private readonly esVersion: EsVersion; constructor( private readonly log: ToolingLog, - private readonly configFile: string, - private readonly configOverrides: any, + private readonly config: Config, esVersion?: string | EsVersion ) { this.esVersion = @@ -47,8 +45,8 @@ export class FunctionalTestRunner { async run(abortSignal?: AbortSignal) { const testStats = await this.getTestStats(); - return await this.runHarness(async (config, lifecycle, coreProviders) => { - SuiteTracker.startTracking(lifecycle, this.configFile); + return await this.runHarness(async (lifecycle, coreProviders) => { + SuiteTracker.startTracking(lifecycle, this.config.path); const realServices = !testStats || (testStats.testCount > 0 && testStats.nonSkippedTestCount > 0); @@ -56,19 +54,19 @@ export class FunctionalTestRunner { const providers = realServices ? new ProviderCollection(this.log, [ ...coreProviders, - ...readProviderSpec('Service', config.get('services')), - ...readProviderSpec('PageObject', config.get('pageObjects')), + ...readProviderSpec('Service', this.config.get('services')), + ...readProviderSpec('PageObject', this.config.get('pageObjects')), ]) - : this.getStubProviderCollection(config, coreProviders); + : this.getStubProviderCollection(coreProviders); if (realServices) { if (providers.hasService('es')) { - await this.validateEsVersion(config); + await this.validateEsVersion(); } await providers.loadAll(); } - const customTestRunner = config.get('testRunner'); + const customTestRunner = this.config.get('testRunner'); if (customTestRunner) { this.log.warning( 'custom test runner defined, ignoring all mocha/suite/filtering related options' @@ -78,7 +76,7 @@ export class FunctionalTestRunner { let reporter; let reporterOptions; - if (config.get('mochaOpts.dryRun')) { + if (this.config.get('mochaOpts.dryRun')) { // override default reporter for dryRun results const targetFile = Path.resolve(REPO_ROOT, 'target/functional-tests/dryRunOutput.json'); reporter = 'json'; @@ -88,22 +86,22 @@ export class FunctionalTestRunner { this.log.info(`Dry run results will be stored in ${targetFile}`); } - const mocha = await setupMocha( + const mocha = await setupMocha({ lifecycle, - this.log, - config, + log: this.log, + config: this.config, providers, - this.esVersion, + esVersion: this.esVersion, reporter, - reporterOptions - ); + reporterOptions, + }); // there's a bug in mocha's dry run, see https://github.com/mochajs/mocha/issues/4838 // until we can update to a mocha version where this is fixed, we won't actually // execute the mocha dry run but simulate it by reading the suites and tests of // the mocha object and writing a report file with similar structure to the json report // (just leave out some execution details like timing, retry and erros) - if (config.get('mochaOpts.dryRun')) { + if (this.config.get('mochaOpts.dryRun')) { return this.simulateMochaDryRun(mocha); } @@ -123,8 +121,8 @@ export class FunctionalTestRunner { }); } - private async validateEsVersion(config: Config) { - const es = createEsClientForFtrConfig(config); + private async validateEsVersion() { + const es = createEsClientForFtrConfig(this.config); let esInfo; try { @@ -151,13 +149,19 @@ export class FunctionalTestRunner { } async getTestStats() { - return await this.runHarness(async (config, lifecycle, coreProviders) => { - if (config.get('testRunner')) { + return await this.runHarness(async (lifecycle, coreProviders) => { + if (this.config.get('testRunner')) { return; } - const providers = this.getStubProviderCollection(config, coreProviders); - const mocha = await setupMocha(lifecycle, this.log, config, providers, this.esVersion); + const providers = this.getStubProviderCollection(coreProviders); + const mocha = await setupMocha({ + lifecycle, + log: this.log, + config: this.config, + providers, + esVersion: this.esVersion, + }); const queue = new Set([mocha.suite]); const allTests: Test[] = []; @@ -178,7 +182,7 @@ export class FunctionalTestRunner { }); } - private getStubProviderCollection(config: Config, coreProviders: Providers) { + private getStubProviderCollection(coreProviders: Providers) { // when we want to load the tests but not actually run anything we can // use stubbed providers which allow mocha to do it's thing without taking // too much time @@ -206,32 +210,30 @@ export class FunctionalTestRunner { ...coreProviders, ...readStubbedProviderSpec( 'Service', - config.get('services'), - config.get('servicesRequiredForTestAnalysis') + this.config.get('services'), + this.config.get('servicesRequiredForTestAnalysis') ), - ...readStubbedProviderSpec('PageObject', config.get('pageObjects'), []), + ...readStubbedProviderSpec('PageObject', this.config.get('pageObjects'), []), ]); } private async runHarness( - handler: (config: Config, lifecycle: Lifecycle, coreProviders: Providers) => Promise + handler: (lifecycle: Lifecycle, coreProviders: Providers) => Promise ): Promise { let runErrorOccurred = false; const lifecycle = new Lifecycle(this.log); try { - const config = await this.readConfigFile(); - this.log.debug('Config loaded'); - if ( - (!config.get('testFiles') || config.get('testFiles').length === 0) && - !config.get('testRunner') + this.config.module.type !== 'journey' && + (!this.config.get('testFiles') || this.config.get('testFiles').length === 0) && + !this.config.get('testRunner') ) { throw new Error('No tests defined.'); } const dockerServers = new DockerServersService( - config.get('dockerServers'), + this.config.get('dockerServers'), this.log, lifecycle ); @@ -240,13 +242,13 @@ export class FunctionalTestRunner { const coreProviders = readProviderSpec('Service', { lifecycle: () => lifecycle, log: () => this.log, - config: () => config, + config: () => this.config, dockerServers: () => dockerServers, esVersion: () => this.esVersion, - dedicatedTaskRunner: () => new DedicatedTaskRunner(config, this.log), + dedicatedTaskRunner: () => new DedicatedTaskRunner(this.config, this.log), }); - return await handler(config, lifecycle, coreProviders); + return await handler(lifecycle, coreProviders); } catch (runError) { runErrorOccurred = true; throw runError; @@ -265,10 +267,6 @@ export class FunctionalTestRunner { } } - public async readConfigFile() { - return await readConfigFile(this.log, this.esVersion, this.configFile, this.configOverrides); - } - simulateMochaDryRun(mocha: any) { interface TestEntry { file: string; diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts index d551e7a884b41..f55a68d3e025c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts @@ -23,6 +23,7 @@ describe('Config', () => { }, primary: true, path: process.cwd(), + module: {} as any, }); expect(config.has('services.foo')).toEqual(true); diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts index d6248b9628e73..6fa2f2acc9046 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts @@ -10,6 +10,7 @@ import { Schema } from 'joi'; import { cloneDeepWith, get, has, toPath } from 'lodash'; import { schema } from './schema'; +import { ConfigModule } from './config_loading'; const $values = Symbol('values'); @@ -17,25 +18,27 @@ interface Options { settings?: Record; primary?: boolean; path: string; + module: ConfigModule; } export class Config { public readonly path: string; + public readonly module: ConfigModule; private [$values]: Record; constructor(options: Options) { - const { settings = {}, primary = false, path = null } = options || {}; - - if (!path) { + if (!options.path) { throw new TypeError('path is a required option'); } - this.path = path; - const { error, value } = schema.validate(settings, { + this.path = options.path; + this.module = options.module; + + const { error, value } = schema.validate(options.settings, { abortEarly: false, context: { - primary: !!primary, - path, + primary: !!options?.primary, + path: options.path, }, }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.test.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config_loading.test.ts similarity index 97% rename from packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.test.ts rename to packages/kbn-test/src/functional_test_runner/lib/config/config_loading.test.ts index 29b723dae7195..a5ccac5edea81 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config_loading.test.ts @@ -7,7 +7,7 @@ */ import { ToolingLog } from '@kbn/tooling-log'; -import { readConfigFile } from './read_config_file'; +import { readConfigFile } from './config_loading'; import { Config } from './config'; import { EsVersion } from '../es_version'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config_loading.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config_loading.ts new file mode 100644 index 0000000000000..cfa2cabec4dfc --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config_loading.ts @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import { ToolingLog } from '@kbn/tooling-log'; +import { defaultsDeep } from 'lodash'; +import { createFlagError, createFailError } from '@kbn/dev-cli-errors'; +import { REPO_ROOT } from '@kbn/utils'; + +import { FtrConfigProvider, GenericFtrProviderContext } from '../../public_types'; +import { Config } from './config'; +import { EsVersion } from '../es_version'; +import { FTR_CONFIGS_MANIFEST_REL, FTR_CONFIGS_MANIFEST_PATHS } from './ftr_configs_manifest'; + +interface LoadSettingsOptions { + path: string; + settingOverrides: any; + primary: boolean; +} + +interface Journey { + config: { + isSkipped(): boolean; + }; + testProvider(ctx: GenericFtrProviderContext): void; +} + +export type ConfigModule = + | { + type: 'config'; + path: string; + provider: FtrConfigProvider; + } + | { + type: 'journey'; + path: string; + provider: FtrConfigProvider; + journey: Journey; + }; + +async function getConfigModule({ + path, + primary, +}: { + path: string; + primary: boolean; +}): Promise { + let resolvedPath; + try { + resolvedPath = require.resolve(path); + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + throw createFlagError(`Unable to find config file [${path}]`); + } + throw error; + } + + if ( + primary && + !FTR_CONFIGS_MANIFEST_PATHS.includes(resolvedPath) && + !resolvedPath.includes(`${Path.sep}__fixtures__${Path.sep}`) + ) { + const rel = Path.relative(REPO_ROOT, resolvedPath); + throw createFlagError( + `Refusing to load FTR Config at [${rel}] which is not listed in [${FTR_CONFIGS_MANIFEST_REL}]. All FTR Config files must be listed there, use the "enabled" key if the FTR Config should be run on automatically on PR CI, or the "disabled" key if it is run manually or by a special job.` + ); + } + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const exports = require(resolvedPath); + const defaultExport = exports.__esModule ? exports.default : exports; + if (typeof defaultExport === 'function') { + return { + type: 'config', + path: resolvedPath, + provider: defaultExport, + }; + } + + const { journey } = exports; + if ( + !journey.constructor || + typeof journey.constructor !== 'function' || + journey.constructor.name !== 'Journey' + ) { + const rel = Path.relative(process.cwd(), resolvedPath); + throw createFailError( + `"journey" export in journey at [${rel}] is not a valid instance of Journey` + ); + } + + return { + type: 'journey', + path: resolvedPath, + provider: journey.constructor.convertToFtrConfigProvider(journey), + journey, + }; +} + +const cache = new WeakMap>(); +async function executeConfigModule( + log: ToolingLog, + esVersion: EsVersion, + options: LoadSettingsOptions, + module: ConfigModule +): Promise { + const cached = cache.get(module.provider); + + if (cached) { + return defaultsDeep({}, options.settingOverrides, await cached); + } + + log.debug(`Loading config file from ${Path.relative(process.cwd(), options.path)}`); + const settings: Promise = module.provider({ + log, + esVersion, + async readConfigFile(p: string) { + const childModule = await getConfigModule({ + primary: false, + path: p, + }); + + return new Config({ + settings: await executeConfigModule( + log, + esVersion, + { + path: childModule.path, + settingOverrides: {}, + primary: false, + }, + childModule + ), + primary: false, + path: p, + module: childModule, + }); + }, + }); + + cache.set(module.provider, Promise.resolve(settings)); + + return defaultsDeep({}, options.settingOverrides, await settings); +} + +const ident = (vars: T) => vars; + +export async function readConfigFile( + log: ToolingLog, + esVersion: EsVersion, + path: string, + settingOverrides: any = {}, + extendSettings: (vars: any) => any = ident +) { + const module = await getConfigModule({ + primary: true, + path, + }); + + return new Config({ + settings: extendSettings( + await executeConfigModule( + log, + esVersion, + { + path, + settingOverrides, + primary: true, + }, + module + ) + ), + primary: true, + path, + module, + }); +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/index.ts b/packages/kbn-test/src/functional_test_runner/lib/config/index.ts index a1f22e215307d..f0fa7e9989c86 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/index.ts @@ -7,5 +7,6 @@ */ export { Config } from './config'; -export { readConfigFile } from './read_config_file'; +export { readConfigFile } from './config_loading'; +export type { ConfigModule } from './config_loading'; export { runCheckFtrConfigsCli } from './run_check_ftr_configs_cli'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.ts b/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.ts deleted file mode 100644 index 142e5c9da9b3b..0000000000000 --- a/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; -import { ToolingLog } from '@kbn/tooling-log'; -import { defaultsDeep } from 'lodash'; -import { createFlagError } from '@kbn/dev-cli-errors'; -import { REPO_ROOT } from '@kbn/utils'; - -import { Config } from './config'; -import { EsVersion } from '../es_version'; -import { FTR_CONFIGS_MANIFEST_REL, FTR_CONFIGS_MANIFEST_PATHS } from './ftr_configs_manifest'; - -const cache = new WeakMap(); - -async function getSettingsFromFile( - log: ToolingLog, - esVersion: EsVersion, - options: { - path: string; - settingOverrides: any; - primary: boolean; - } -) { - let resolvedPath; - try { - resolvedPath = require.resolve(options.path); - } catch (error) { - if (error.code === 'MODULE_NOT_FOUND') { - throw createFlagError(`Unable to find config file [${options.path}]`); - } - - throw error; - } - - if ( - options.primary && - !FTR_CONFIGS_MANIFEST_PATHS.includes(resolvedPath) && - !resolvedPath.includes(`${Path.sep}__fixtures__${Path.sep}`) - ) { - const rel = Path.relative(REPO_ROOT, resolvedPath); - throw createFlagError( - `Refusing to load FTR Config at [${rel}] which is not listed in [${FTR_CONFIGS_MANIFEST_REL}]. All FTR Config files must be listed there, use the "enabled" key if the FTR Config should be run on automatically on PR CI, or the "disabled" key if it is run manually or by a special job.` - ); - } - - const configModule = require(resolvedPath); // eslint-disable-line @typescript-eslint/no-var-requires - const configProvider = configModule.__esModule ? configModule.default : configModule; - - if (!cache.has(configProvider)) { - log.debug('Loading config file from %j', resolvedPath); - cache.set( - configProvider, - configProvider({ - log, - esVersion, - async readConfigFile(p: string, o: any) { - return new Config({ - settings: await getSettingsFromFile(log, esVersion, { - path: p, - settingOverrides: o, - primary: false, - }), - primary: false, - path: p, - }); - }, - }) - ); - } - - const settingsWithDefaults: any = defaultsDeep( - {}, - options.settingOverrides, - await cache.get(configProvider)! - ); - - return settingsWithDefaults; -} - -export async function readConfigFile( - log: ToolingLog, - esVersion: EsVersion, - path: string, - settingOverrides: any = {} -) { - return new Config({ - settings: await getSettingsFromFile(log, esVersion, { - path, - settingOverrides, - primary: true, - }), - primary: true, - path, - }); -} diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 7e7ba9e26eb48..ce44dd3cc0496 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -14,7 +14,6 @@ import type { CustomHelpers } from 'joi'; // valid pattern for ID // enforced camel-case identifiers for consistency const ID_PATTERN = /^[a-zA-Z0-9_]+$/; -const SCALABILITY_DURATION_PATTERN = /^[1-9]\d{0,}[m|s]$/; // it will search both --inspect and --inspect-brk const INSPECTING = !!process.execArgv.find((arg) => arg.includes('--inspect')); @@ -164,6 +163,7 @@ export const schema = Joi.object() .keys({ enabled: Joi.boolean().default(!!process.env.CI && !process.env.DISABLE_JUNIT_REPORTER), reportName: Joi.string(), + metadata: Joi.object().unknown(true).default(), }) .default(), @@ -267,63 +267,6 @@ export const schema = Joi.object() }) .default(), - /** - * Optional settings to list test data archives, that will be loaded during the 'beforeTests' - * lifecycle phase and unloaded during the 'cleanup' lifecycle phase. - */ - testData: Joi.object() - .keys({ - kbnArchives: Joi.array().items(Joi.string()).default([]), - esArchives: Joi.array().items(Joi.string()).default([]), - }) - .default(), - - /** - * Optional settings to enable scalability testing for single user performance journey. - * If defined, 'scalabilitySetup' must include 'warmup' and 'test' stage array, - * 'maxDuration', e.g. '10m' to limit execution time to 10 minutes. - * Each stage must include 'action', 'duration' and 'maxUsersCount'. - * In addition, 'rampConcurrentUsers' requires 'minUsersCount' to ramp users from - * min to max within provided time duration. - */ - scalabilitySetup: Joi.object() - .keys({ - warmup: Joi.array() - .items( - Joi.object().keys({ - action: Joi.string() - .valid('constantConcurrentUsers', 'rampConcurrentUsers') - .required(), - duration: Joi.string().pattern(SCALABILITY_DURATION_PATTERN).required(), - minUsersCount: Joi.number().when('action', { - is: 'rampConcurrentUsers', - then: Joi.number().required().less(Joi.ref('maxUsersCount')), - otherwise: Joi.forbidden(), - }), - maxUsersCount: Joi.number().required().greater(0), - }) - ) - .required(), - test: Joi.array() - .items( - Joi.object().keys({ - action: Joi.string() - .valid('constantConcurrentUsers', 'rampConcurrentUsers') - .required(), - duration: Joi.string().pattern(SCALABILITY_DURATION_PATTERN).required(), - minUsersCount: Joi.number().when('action', { - is: 'rampConcurrentUsers', - then: Joi.number().required().less(Joi.ref('maxUsersCount')), - otherwise: Joi.forbidden(), - }), - maxUsersCount: Joi.number().required().greater(0), - }) - ) - .required(), - maxDuration: Joi.string().pattern(SCALABILITY_DURATION_PATTERN).required(), - }) - .optional(), - // settings for the kibanaServer.uiSettings module uiSettings: Joi.object() .keys({ diff --git a/packages/kbn-test/src/functional_test_runner/lib/es_version.ts b/packages/kbn-test/src/functional_test_runner/lib/es_version.ts index 8b3acde47a4dc..976a2c417c747 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/es_version.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/es_version.ts @@ -11,6 +11,10 @@ import { kibanaPackageJson } from '@kbn/utils'; export class EsVersion { static getDefault() { + if (typeof jest === 'object' && jest) { + return new EsVersion('9.9.9'); + } + // example: https://storage.googleapis.com/kibana-ci-es-snapshots-daily/8.0.0/manifest-latest-verified.json const manifestUrl = process.env.ES_SNAPSHOT_MANIFEST; if (manifestUrl) { diff --git a/packages/kbn-test/src/functional_test_runner/lib/index.ts b/packages/kbn-test/src/functional_test_runner/lib/index.ts index 983a185dee682..3b12849a9ec13 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/index.ts @@ -8,7 +8,7 @@ export { Lifecycle } from './lifecycle'; export { LifecyclePhase } from './lifecycle_phase'; -export { readConfigFile, Config, runCheckFtrConfigsCli } from './config'; +export * from './config'; export * from './providers'; // @internal export { runTests, setupMocha } from './mocha'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js deleted file mode 100644 index 7c4a6ddcd1430..0000000000000 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { isAbsolute } from 'path'; - -import { loadTracer } from '../load_tracer'; -import { decorateMochaUi } from './decorate_mocha_ui'; -import { decorateSnapshotUi } from '../snapshots/decorate_snapshot_ui'; - -/** - * Load an array of test files into a mocha instance - * - * @param {Mocha} mocha - * @param {ToolingLog} log - * @param {Config} config - * @param {ProviderCollection} providers - * @param {String} path - * @return {undefined} - mutates mocha, no return value - */ -export const loadTestFiles = ({ - mocha, - log, - config, - lifecycle, - providers, - paths, - updateBaselines, - updateSnapshots, -}) => { - const dockerServers = config.get('dockerServers'); - const isDockerGroup = dockerServers && Object.keys(dockerServers).length; - - decorateSnapshotUi({ lifecycle, updateSnapshots, isCi: !!process.env.CI }); - - const innerLoadTestFile = (path) => { - if (typeof path !== 'string' || !isAbsolute(path)) { - throw new TypeError('loadTestFile() only accepts absolute paths'); - } - - loadTracer(path, `testFile[${path}]`, () => { - log.verbose('Loading test file %s', path); - - const testModule = require(path); // eslint-disable-line import/no-dynamic-require - const testProvider = testModule.__esModule ? testModule.default : testModule; - - runTestProvider(testProvider, path); // eslint-disable-line no-use-before-define - }); - }; - - const runTestProvider = (provider, path) => { - if (typeof provider !== 'function') { - throw new Error(`Default export of test files must be a function, got ${provider}`); - } - - loadTracer(provider, `testProvider[${path}]`, () => { - // mocha.suite hocus-pocus comes from: https://git.io/vDnXO - - const context = decorateMochaUi(log, lifecycle, global, { - isDockerGroup, - rootTags: config.get('rootTags'), - }); - mocha.suite.emit('pre-require', context, path, mocha); - - const returnVal = provider({ - loadTestFile: innerLoadTestFile, - getService: providers.getService, - getPageObject: providers.getPageObject, - getPageObjects: providers.getPageObjects, - updateBaselines, - }); - - if (returnVal && typeof returnVal.then === 'function') { - throw new TypeError('Default export of test files must not be an async function'); - } - - mocha.suite.emit('require', returnVal, path, mocha); - mocha.suite.emit('post-require', global, path, mocha); - - context.revertProxiedAssignments(); - }); - }; - - paths.forEach(innerLoadTestFile); -}; diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_tests.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_tests.ts new file mode 100644 index 0000000000000..32f61caf1b3c7 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_tests.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 { isAbsolute } from 'path'; + +import type { ToolingLog } from '@kbn/tooling-log'; + +import type { Config } from '../config'; +import type { GenericFtrProviderContext } from '../../public_types'; +import type { Lifecycle } from '../lifecycle'; +import type { ProviderCollection } from '../providers'; +import { loadTracer } from '../load_tracer'; +import { decorateSnapshotUi } from '../snapshots/decorate_snapshot_ui'; + +// @ts-expect-error not js yet +import { decorateMochaUi } from './decorate_mocha_ui'; + +type TestProvider = (ctx: GenericFtrProviderContext) => void; + +interface Options { + mocha: any; + log: ToolingLog; + config: Config; + lifecycle: Lifecycle; + providers: ProviderCollection; + paths: string[]; + updateBaselines: boolean; + updateSnapshots: boolean; +} + +const isObj = (v: unknown): v is Record => typeof v === 'object' && v !== null; + +/** + * Load an array of test files or a test provider into a mocha instance + */ +export const loadTests = ({ + mocha, + log, + config, + lifecycle, + providers, + paths, + updateBaselines, + updateSnapshots, +}: Options) => { + const dockerServers = config.get('dockerServers'); + const isDockerGroup = dockerServers && Object.keys(dockerServers).length; + + const ctx: GenericFtrProviderContext = { + loadTestFile, + getService: providers.getService as any, + hasService: providers.hasService as any, + getPageObject: providers.getPageObject as any, + getPageObjects: providers.getPageObjects as any, + updateBaselines, + }; + + decorateSnapshotUi({ lifecycle, updateSnapshots, isCi: !!process.env.CI }); + + function loadTestFile(path: string) { + if (typeof path !== 'string' || !isAbsolute(path)) { + throw new TypeError('loadTestFile() only accepts absolute paths'); + } + + loadTracer(path, `testFile[${path}]`, () => { + log.verbose('Loading test file %s', path); + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const testModule = require(path); + const testProvider = testModule.__esModule ? testModule.default : testModule; + + runTestProvider(testProvider, path); + }); + } + + function withMocha(debugPath: string, fn: () => void) { + // mocha.suite hocus-pocus comes from: https://git.io/vDnXO + const context = decorateMochaUi(log, lifecycle, global, { + isDockerGroup, + rootTags: config.get('rootTags'), + }); + mocha.suite.emit('pre-require', context, debugPath, mocha); + + fn(); + + mocha.suite.emit('require', undefined, debugPath, mocha); + mocha.suite.emit('post-require', global, debugPath, mocha); + + context.revertProxiedAssignments(); + } + + function runTestProvider(provider: TestProvider, path: string) { + if (typeof provider !== 'function') { + throw new Error(`Default export of test files must be a function, got ${provider}`); + } + + loadTracer(provider, `testProvider[${path}]`, () => { + withMocha(path, () => { + const returnVal = provider(ctx); + if (isObj(returnVal) && typeof returnVal.then === 'function') { + throw new TypeError('Test file providers must not be async or return promises'); + } + }); + }); + } + + const cm = config.module; + if (cm.type === 'journey') { + withMocha(cm.path, () => { + cm.journey.testProvider(ctx); + }); + } else { + paths.forEach(loadTestFile); + } +}; diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js index 66a4c9ce4fd04..b2bea7b079c9b 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js @@ -47,6 +47,7 @@ export function MochaReporterProvider({ getService }) { if (config.get('junit.enabled') && config.get('junit.reportName')) { setupJUnitReportGeneration(runner, { reportName: config.get('junit.reportName'), + metadata: config.get('junit.metadata'), }); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.ts similarity index 59% rename from packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js rename to packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.ts index 9261391c5bf6a..10c51517aec94 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.ts @@ -6,34 +6,50 @@ * Side Public License, v 1. */ -import Mocha from 'mocha'; import { relative } from 'path'; + import { REPO_ROOT } from '@kbn/utils'; +import { ToolingLog } from '@kbn/tooling-log'; -import { loadTestFiles } from './load_test_files'; +import { Suite } from '../../fake_mocha_types'; +import { loadTests } from './load_tests'; import { filterSuites } from './filter_suites'; +import { Lifecycle } from '../lifecycle'; +import { Config } from '../config'; +import { ProviderCollection } from '../providers'; +import { EsVersion } from '../es_version'; + +// @ts-expect-error not ts yet import { MochaReporterProvider } from './reporter'; +// @ts-expect-error not ts yet import { validateCiGroupTags } from './validate_ci_group_tags'; +interface Options { + lifecycle: Lifecycle; + log: ToolingLog; + config: Config; + providers: ProviderCollection; + esVersion: EsVersion; + reporter?: any; + reporterOptions?: any; +} + +// we use require so that @types/mocha isn't loaded +const Mocha = require('mocha'); // eslint-disable-line @typescript-eslint/no-var-requires + /** * Instantiate mocha and load testfiles into it - * - * @param {Lifecycle} lifecycle - * @param {ToolingLog} log - * @param {Config} config - * @param {ProviderCollection} providers - * @param {EsVersion} esVersion * @return {Promise} */ -export async function setupMocha( +export async function setupMocha({ lifecycle, log, config, providers, esVersion, reporter, - reporterOptions -) { + reporterOptions, +}: Options) { // configure mocha const mocha = new Mocha({ ...config.get('mochaOpts'), @@ -43,19 +59,19 @@ export async function setupMocha( }); // global beforeEach hook in root suite triggers before all others - mocha.suite.beforeEach('global before each', async function () { - await lifecycle.beforeEachTest.trigger(this.currentTest); + mocha.suite.beforeEach('global before each', async function (this: Suite) { + await lifecycle.beforeEachTest.trigger(this.currentTest!); }); - loadTestFiles({ + loadTests({ mocha, log, config, lifecycle, providers, - paths: config.get('testFiles'), updateBaselines: config.get('updateBaselines'), updateSnapshots: config.get('updateSnapshots'), + paths: config.get('testFiles'), }); // valiate that there aren't any tests in multiple ciGroups @@ -76,15 +92,15 @@ export async function setupMocha( filterSuites({ log, mocha, - include: config.get('suiteFiles.include').map((file) => relative(REPO_ROOT, file)), - exclude: config.get('suiteFiles.exclude').map((file) => relative(REPO_ROOT, file)), + include: config.get('suiteFiles.include').map((file: string) => relative(REPO_ROOT, file)), + exclude: config.get('suiteFiles.exclude').map((file: string) => relative(REPO_ROOT, file)), }); filterSuites({ log, mocha, - include: config.get('suiteTags.include').map((tag) => tag.replace(/-\d+$/, '')), - exclude: config.get('suiteTags.exclude').map((tag) => tag.replace(/-\d+$/, '')), + include: config.get('suiteTags.include').map((tag: string) => tag.replace(/-\d+$/, '')), + exclude: config.get('suiteTags.exclude').map((tag: string) => tag.replace(/-\d+$/, '')), }); return mocha; diff --git a/packages/kbn-test/src/functional_test_runner/public_types.ts b/packages/kbn-test/src/functional_test_runner/public_types.ts index 3faa19de73ce1..248b914b2ebe7 100644 --- a/packages/kbn-test/src/functional_test_runner/public_types.ts +++ b/packages/kbn-test/src/functional_test_runner/public_types.ts @@ -105,6 +105,11 @@ export interface GenericFtrProviderContext< * @param path */ loadTestFile(path: string): void; + + /** + * Did the user request that baselines get updated? + */ + updateBaselines: boolean; } export class GenericFtrService> { @@ -117,4 +122,6 @@ export interface FtrConfigProviderContext { readConfigFile(path: string): Promise; } +export type FtrConfigProvider = (ctx: FtrConfigProviderContext) => T | Promise; + export type { Test, Suite }; diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap deleted file mode 100644 index ff8961e263f17..0000000000000 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap +++ /dev/null @@ -1,332 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`display help for run tests CLI displays as expected 1`] = ` -"Run Functional Tests - -Usage: - node scripts/functional_tests --help - node scripts/functional_tests [--config [--config ...]] - node scripts/functional_tests [options] [-- --] - -Options: - --help Display this menu and exit. - --config Pass in a config. Can pass in multiple configs. - --esFrom Build Elasticsearch from source or run from snapshot. Default: $TEST_ES_FROM or snapshot - --kibana-install-dir Run Kibana from existing install directory instead of from source. - --bail Stop the test run at the first failure. - --grep Pattern to select which tests to run. - --updateBaselines Replace baseline screenshots with whatever is generated from the test. - --updateSnapshots Replace inline and file snapshots with whatever is generated from the test. - --u Replace both baseline screenshots and snapshots - --include Files that must included to be run, can be included multiple times. - --exclude Files that must NOT be included to be run, can be included multiple times. - --include-tag Tags that suites must include to be run, can be included multiple times. - --exclude-tag Tags that suites must NOT include to be run, can be included multiple times. - --assert-none-excluded Exit with 1/0 based on if any test is excluded with the current set of tags. - --logToFile Write the log output from Kibana/Elasticsearch to files instead of to stdout - --verbose Log everything. - --debug Run in debug mode. - --quiet Only log errors. - --silent Log nothing. - --dry-run Report tests without executing them." -`; - -exports[`process options for run tests CLI accepts boolean value for updateBaselines 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, - "updateBaselines": true, -} -`; - -exports[`process options for run tests CLI accepts boolean value for updateSnapshots 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, - "updateSnapshots": true, -} -`; - -exports[`process options for run tests CLI accepts debug option 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "debug": true, - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts empty config value if default passed 1`] = ` -Object { - "assertNoneExcluded": false, - "config": "", - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts extra server options 1`] = ` -Object { - "_": Object { - "server.foo": "bar", - }, - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": Object { - "server.foo": "bar", - }, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts quiet option 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "quiet": true, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts silent option 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "silent": true, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts source value for $TEST_ES_FROM 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "source", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts source value for esFrom 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "source", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts string value for kibana-install-dir 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "installDir": "foo", - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts value for grep 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "grep": "management", - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; - -exports[`process options for run tests CLI accepts verbose option 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, - "verbose": true, -} -`; - -exports[`process options for run tests CLI prioritizes source flag over $TEST_ES_FROM 1`] = ` -Object { - "assertNoneExcluded": false, - "configs": Array [ - /foo, - ], - "createLogger": [Function], - "esFrom": "snapshot", - "esVersion": "999.999.999", - "extraKbnOpts": undefined, - "logsDir": undefined, - "suiteFiles": Object { - "exclude": Array [], - "include": Array [], - }, - "suiteTags": Object { - "exclude": Array [], - "include": Array [], - }, -} -`; diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js deleted file mode 100644 index 8b1bf471f4e98..0000000000000 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; - -import { v4 as uuid } from 'uuid'; -import dedent from 'dedent'; -import { REPO_ROOT } from '@kbn/utils'; -import { ToolingLog, pickLevelFromFlags } from '@kbn/tooling-log'; -import { EsVersion } from '../../../functional_test_runner'; - -const options = { - help: { desc: 'Display this menu and exit.' }, - config: { - arg: '', - desc: 'Pass in a config. Can pass in multiple configs.', - }, - esFrom: { - arg: '', - choices: ['snapshot', 'source'], - desc: 'Build Elasticsearch from source or run from snapshot.', - defaultHelp: 'Default: $TEST_ES_FROM or snapshot', - }, - 'kibana-install-dir': { - arg: '', - desc: 'Run Kibana from existing install directory instead of from source.', - }, - bail: { desc: 'Stop the test run at the first failure.' }, - grep: { - arg: '', - desc: 'Pattern to select which tests to run.', - }, - updateBaselines: { - desc: 'Replace baseline screenshots with whatever is generated from the test.', - }, - updateSnapshots: { - desc: 'Replace inline and file snapshots with whatever is generated from the test.', - }, - u: { - desc: 'Replace both baseline screenshots and snapshots', - }, - include: { - arg: '', - desc: 'Files that must included to be run, can be included multiple times.', - }, - exclude: { - arg: '', - desc: 'Files that must NOT be included to be run, can be included multiple times.', - }, - 'include-tag': { - arg: '', - desc: 'Tags that suites must include to be run, can be included multiple times.', - }, - 'exclude-tag': { - arg: '', - desc: 'Tags that suites must NOT include to be run, can be included multiple times.', - }, - 'assert-none-excluded': { - desc: 'Exit with 1/0 based on if any test is excluded with the current set of tags.', - }, - logToFile: { - desc: 'Write the log output from Kibana/Elasticsearch to files instead of to stdout', - }, - verbose: { desc: 'Log everything.' }, - debug: { desc: 'Run in debug mode.' }, - quiet: { desc: 'Only log errors.' }, - silent: { desc: 'Log nothing.' }, - 'dry-run': { desc: 'Report tests without executing them.' }, -}; - -export function displayHelp() { - const helpOptions = Object.keys(options) - .filter((name) => name !== '_') - .map((name) => { - const option = options[name]; - return { - ...option, - usage: `${name} ${option.arg || ''}`, - default: option.defaultHelp || '', - }; - }) - .map((option) => { - return `--${option.usage.padEnd(28)} ${option.desc} ${option.default}`; - }) - .join(`\n `); - - return dedent(` - Run Functional Tests - - Usage: - node scripts/functional_tests --help - node scripts/functional_tests [--config [--config ...]] - node scripts/functional_tests [options] [-- --] - - Options: - ${helpOptions} - `); -} - -export function processOptions(userOptions, defaultConfigPaths) { - validateOptions(userOptions); - - let configs; - if (userOptions.config) { - configs = [].concat(userOptions.config); - } else { - if (!defaultConfigPaths || defaultConfigPaths.length === 0) { - throw new Error(`functional_tests: config is required`); - } else { - configs = defaultConfigPaths; - } - } - - if (!userOptions.esFrom) { - userOptions.esFrom = process.env.TEST_ES_FROM || 'snapshot'; - } - - if (userOptions['kibana-install-dir']) { - userOptions.installDir = userOptions['kibana-install-dir']; - delete userOptions['kibana-install-dir']; - } - - userOptions.suiteFiles = { - include: [].concat(userOptions.include || []), - exclude: [].concat(userOptions.exclude || []), - }; - delete userOptions.include; - delete userOptions.exclude; - - userOptions.suiteTags = { - include: [].concat(userOptions['include-tag'] || []), - exclude: [].concat(userOptions['exclude-tag'] || []), - }; - delete userOptions['include-tag']; - delete userOptions['exclude-tag']; - - userOptions.assertNoneExcluded = !!userOptions['assert-none-excluded']; - delete userOptions['assert-none-excluded']; - - if (userOptions['dry-run']) { - userOptions.dryRun = userOptions['dry-run']; - delete userOptions['dry-run']; - } - - const log = new ToolingLog({ - level: pickLevelFromFlags(userOptions), - writeTo: process.stdout, - }); - function createLogger() { - return log; - } - - const logToFile = !!userOptions.logToFile; - const logsDir = logToFile ? Path.resolve(REPO_ROOT, 'data/ftr_servers_logs', uuid()) : undefined; - - return { - ...userOptions, - configs: configs.map((c) => Path.resolve(c)), - createLogger, - extraKbnOpts: userOptions._, - esVersion: EsVersion.getDefault(), - logsDir, - }; -} - -function validateOptions(userOptions) { - Object.entries(userOptions).forEach(([key, val]) => { - if (key === '_' || key === 'suiteTags') { - return; - } - - // Validate flags passed - if (options[key] === undefined) { - throw new Error(`functional_tests: invalid option [${key}]`); - } - - if ( - // Validate boolean flags - (!options[key].arg && typeof val !== 'boolean') || - // Validate string/array flags - (options[key].arg && typeof val !== 'string' && !Array.isArray(val)) || - // Validate enum flags - (options[key].choices && !options[key].choices.includes(val)) - ) { - throw new Error(`functional_tests: invalid argument [${val}] to option [${key}]`); - } - }); -} diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js deleted file mode 100644 index 888708a2b9d69..0000000000000 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { createAbsolutePathSerializer } from '@kbn/jest-serializers'; - -import { displayHelp, processOptions } from './args'; - -jest.mock('../../../functional_test_runner/lib/es_version', () => { - return { - EsVersion: class { - static getDefault() { - return '999.999.999'; - } - }, - }; -}); - -expect.addSnapshotSerializer(createAbsolutePathSerializer(process.cwd())); - -const INITIAL_TEST_ES_FROM = process.env.TEST_ES_FROM; -beforeEach(() => { - process.env.TEST_ES_FROM = 'snapshot'; -}); -afterEach(() => { - process.env.TEST_ES_FROM = INITIAL_TEST_ES_FROM; -}); - -describe('display help for run tests CLI', () => { - it('displays as expected', () => { - expect(displayHelp()).toMatchSnapshot(); - }); -}); - -describe('process options for run tests CLI', () => { - it('rejects boolean config value', () => { - expect(() => { - processOptions({ config: true }); - }).toThrow('functional_tests: invalid argument [true] to option [config]'); - }); - - it('rejects empty config value if no default passed', () => { - expect(() => { - processOptions({}); - }).toThrow('functional_tests: config is required'); - }); - - it('accepts empty config value if default passed', () => { - const options = processOptions({ config: '' }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('rejects non-boolean value for bail', () => { - expect(() => { - processOptions({ bail: 'peanut' }, ['foo']); - }).toThrow('functional_tests: invalid argument [peanut] to option [bail]'); - }); - - it('accepts string value for kibana-install-dir', () => { - const options = processOptions({ 'kibana-install-dir': 'foo' }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('rejects boolean value for kibana-install-dir', () => { - expect(() => { - processOptions({ 'kibana-install-dir': true }, ['foo']); - }).toThrow('functional_tests: invalid argument [true] to option [kibana-install-dir]'); - }); - - it('accepts boolean value for updateBaselines', () => { - const options = processOptions({ updateBaselines: true }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('accepts boolean value for updateSnapshots', () => { - const options = processOptions({ updateSnapshots: true }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('accepts source value for esFrom', () => { - const options = processOptions({ esFrom: 'source' }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('accepts source value for $TEST_ES_FROM', () => { - process.env.TEST_ES_FROM = 'source'; - const options = processOptions({}, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('prioritizes source flag over $TEST_ES_FROM', () => { - process.env.TEST_ES_FROM = 'source'; - const options = processOptions({ esFrom: 'snapshot' }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('rejects non-enum value for esFrom', () => { - expect(() => { - processOptions({ esFrom: 'butter' }, ['foo']); - }).toThrow('functional_tests: invalid argument [butter] to option [esFrom]'); - }); - - it('accepts value for grep', () => { - const options = processOptions({ grep: 'management' }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('accepts debug option', () => { - const options = processOptions({ debug: true }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('accepts silent option', () => { - const options = processOptions({ silent: true }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('accepts quiet option', () => { - const options = processOptions({ quiet: true }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('accepts verbose option', () => { - const options = processOptions({ verbose: true }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('accepts extra server options', () => { - const options = processOptions({ _: { 'server.foo': 'bar' } }, ['foo']); - expect(options).toMatchSnapshot(); - }); - - it('rejects invalid options even if valid options exist', () => { - expect(() => { - processOptions({ debug: true, aintnothang: true, bail: true }, ['foo']); - }).toThrow('functional_tests: invalid option [aintnothang]'); - }); -}); diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.js b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.js deleted file mode 100644 index 3958c1503cd30..0000000000000 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { runTests, initLogsDir } from '../../tasks'; -import { runCli } from '../../lib'; -import { processOptions, displayHelp } from './args'; - -/** - * Run servers and tests for each config - * Only cares about --config option. Other options - * are passed directly to functional_test_runner, such as - * --bail, --verbose, etc. - * @param {string[]} defaultConfigPaths Optional paths to configs - * if no config option is passed - */ -export async function runTestsCli(defaultConfigPaths) { - await runCli(displayHelp, async (userOptions) => { - const options = processOptions(userOptions, defaultConfigPaths); - initLogsDir(options); - await runTests(options); - }); -} diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/args.test.js.snap b/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/args.test.js.snap deleted file mode 100644 index 1f572578119f7..0000000000000 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/args.test.js.snap +++ /dev/null @@ -1,141 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`display help for start servers CLI displays as expected 1`] = ` -"Start Functional Test Servers - -Usage: - node scripts/functional_tests_server --help - node scripts/functional_tests_server [--config ] - node scripts/functional_tests_server [options] [-- --] - -Options: - --help Display this menu and exit. - --config Pass in a config - --esFrom Build Elasticsearch from source, snapshot or path to existing install dir. Default: $TEST_ES_FROM or snapshot - --kibana-install-dir Run Kibana from existing install directory instead of from source. - --logToFile Write the log output from Kibana/Elasticsearch to files instead of to stdout - --verbose Log everything. - --debug Run in debug mode. - --quiet Only log errors. - --silent Log nothing." -`; - -exports[`process options for start servers CLI accepts debug option 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "debug": true, - "esFrom": "snapshot", - "extraKbnOpts": undefined, - "logsDir": undefined, - "useDefaultConfig": true, -} -`; - -exports[`process options for start servers CLI accepts empty config value if default passed 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "esFrom": "snapshot", - "extraKbnOpts": undefined, - "logsDir": undefined, - "useDefaultConfig": true, -} -`; - -exports[`process options for start servers CLI accepts extra server options 1`] = ` -Object { - "_": Object { - "server.foo": "bar", - }, - "config": /foo, - "createLogger": [Function], - "esFrom": "snapshot", - "extraKbnOpts": Object { - "server.foo": "bar", - }, - "logsDir": undefined, - "useDefaultConfig": true, -} -`; - -exports[`process options for start servers CLI accepts quiet option 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "esFrom": "snapshot", - "extraKbnOpts": undefined, - "logsDir": undefined, - "quiet": true, - "useDefaultConfig": true, -} -`; - -exports[`process options for start servers CLI accepts silent option 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "esFrom": "snapshot", - "extraKbnOpts": undefined, - "logsDir": undefined, - "silent": true, - "useDefaultConfig": true, -} -`; - -exports[`process options for start servers CLI accepts source value for $TEST_ES_FROM 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "esFrom": "source", - "extraKbnOpts": undefined, - "logsDir": undefined, - "useDefaultConfig": true, -} -`; - -exports[`process options for start servers CLI accepts source value for esFrom 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "esFrom": "source", - "extraKbnOpts": undefined, - "logsDir": undefined, - "useDefaultConfig": true, -} -`; - -exports[`process options for start servers CLI accepts string value for kibana-install-dir 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "esFrom": "snapshot", - "extraKbnOpts": undefined, - "installDir": "foo", - "logsDir": undefined, - "useDefaultConfig": true, -} -`; - -exports[`process options for start servers CLI accepts verbose option 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "esFrom": "snapshot", - "extraKbnOpts": undefined, - "logsDir": undefined, - "useDefaultConfig": true, - "verbose": true, -} -`; - -exports[`process options for start servers CLI prioritizes source flag over $TEST_ES_FROM 1`] = ` -Object { - "config": /foo, - "createLogger": [Function], - "esFrom": "snapshot", - "extraKbnOpts": undefined, - "logsDir": undefined, - "useDefaultConfig": true, -} -`; diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/args.js b/packages/kbn-test/src/functional_tests/cli/start_servers/args.js deleted file mode 100644 index e025bdc339331..0000000000000 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/args.js +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; - -import { v4 as uuid } from 'uuid'; -import dedent from 'dedent'; -import { REPO_ROOT } from '@kbn/utils'; -import { ToolingLog, pickLevelFromFlags } from '@kbn/tooling-log'; - -const options = { - help: { desc: 'Display this menu and exit.' }, - config: { - arg: '', - desc: 'Pass in a config', - }, - esFrom: { - arg: '', - desc: 'Build Elasticsearch from source, snapshot or path to existing install dir.', - defaultHelp: 'Default: $TEST_ES_FROM or snapshot', - }, - 'kibana-install-dir': { - arg: '', - desc: 'Run Kibana from existing install directory instead of from source.', - }, - logToFile: { - desc: 'Write the log output from Kibana/Elasticsearch to files instead of to stdout', - }, - verbose: { desc: 'Log everything.' }, - debug: { desc: 'Run in debug mode.' }, - quiet: { desc: 'Only log errors.' }, - silent: { desc: 'Log nothing.' }, -}; - -export function displayHelp() { - const helpOptions = Object.keys(options) - .filter((name) => name !== '_') - .map((name) => { - const option = options[name]; - return { - ...option, - usage: `${name} ${option.arg || ''}`, - default: option.defaultHelp || '', - }; - }) - .map((option) => { - return `--${option.usage.padEnd(30)} ${option.desc} ${option.default}`; - }) - .join(`\n `); - - return dedent(` - Start Functional Test Servers - - Usage: - node scripts/functional_tests_server --help - node scripts/functional_tests_server [--config ] - node scripts/functional_tests_server [options] [-- --] - - Options: - ${helpOptions} - `); -} - -export function processOptions(userOptions, defaultConfigPath) { - validateOptions(userOptions); - - const useDefaultConfig = !userOptions.config; - const config = useDefaultConfig ? defaultConfigPath : userOptions.config; - - if (!config) { - throw new Error(`functional_tests_server: config is required`); - } - - if (!userOptions.esFrom) { - userOptions.esFrom = process.env.TEST_ES_FROM || 'snapshot'; - } - - if (userOptions['kibana-install-dir']) { - userOptions.installDir = userOptions['kibana-install-dir']; - delete userOptions['kibana-install-dir']; - } - - const log = new ToolingLog({ - level: pickLevelFromFlags(userOptions), - writeTo: process.stdout, - }); - - function createLogger() { - return log; - } - - const logToFile = !!userOptions.logToFile; - const logsDir = logToFile ? Path.resolve(REPO_ROOT, 'data/ftr_servers_logs', uuid()) : undefined; - - return { - ...userOptions, - logsDir, - config: Path.resolve(config), - useDefaultConfig, - createLogger, - extraKbnOpts: userOptions._, - }; -} - -function validateOptions(userOptions) { - Object.entries(userOptions).forEach(([key, val]) => { - if (key === '_') return; - - // Validate flags passed - if (options[key] === undefined) { - throw new Error(`functional_tests_server: invalid option [${key}]`); - } - - if ( - // Validate boolean flags - (!options[key].arg && typeof val !== 'boolean') || - // Validate string/array flags - (options[key].arg && typeof val !== 'string' && !Array.isArray(val)) || - // Validate enum flags - (options[key].choices && !options[key].choices.includes(val)) - ) { - throw new Error(`functional_tests_server: invalid argument [${val}] to option [${key}]`); - } - }); -} diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/args.test.js b/packages/kbn-test/src/functional_tests/cli/start_servers/args.test.js deleted file mode 100644 index 7d6c77be2539e..0000000000000 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/args.test.js +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { displayHelp, processOptions } from './args'; -import { createAbsolutePathSerializer } from '@kbn/jest-serializers'; - -expect.addSnapshotSerializer(createAbsolutePathSerializer(process.cwd())); - -const INITIAL_TEST_ES_FROM = process.env.TEST_ES_FROM; -beforeEach(() => { - process.env.TEST_ES_FROM = 'snapshot'; -}); -afterEach(() => { - process.env.TEST_ES_FROM = INITIAL_TEST_ES_FROM; -}); - -describe('display help for start servers CLI', () => { - it('displays as expected', () => { - expect(displayHelp()).toMatchSnapshot(); - }); -}); - -describe('process options for start servers CLI', () => { - it('rejects boolean config value', () => { - expect(() => { - processOptions({ config: true }); - }).toThrow('functional_tests_server: invalid argument [true] to option [config]'); - }); - - it('rejects empty config value if no default passed', () => { - expect(() => { - processOptions({}); - }).toThrow('functional_tests_server: config is required'); - }); - - it('accepts empty config value if default passed', () => { - const options = processOptions({ config: '' }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('rejects invalid option', () => { - expect(() => { - processOptions({ bail: true }, 'foo'); - }).toThrow('functional_tests_server: invalid option [bail]'); - }); - - it('accepts string value for kibana-install-dir', () => { - const options = processOptions({ 'kibana-install-dir': 'foo' }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('rejects boolean value for kibana-install-dir', () => { - expect(() => { - processOptions({ 'kibana-install-dir': true }, 'foo'); - }).toThrow('functional_tests_server: invalid argument [true] to option [kibana-install-dir]'); - }); - - it('accepts source value for esFrom', () => { - const options = processOptions({ esFrom: 'source' }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('accepts source value for $TEST_ES_FROM', () => { - process.env.TEST_ES_FROM = 'source'; - const options = processOptions({}, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('prioritizes source flag over $TEST_ES_FROM', () => { - process.env.TEST_ES_FROM = 'source'; - const options = processOptions({ esFrom: 'snapshot' }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('accepts debug option', () => { - const options = processOptions({ debug: true }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('accepts silent option', () => { - const options = processOptions({ silent: true }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('accepts quiet option', () => { - const options = processOptions({ quiet: true }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('accepts verbose option', () => { - const options = processOptions({ verbose: true }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('accepts extra server options', () => { - const options = processOptions({ _: { 'server.foo': 'bar' } }, 'foo'); - expect(options).toMatchSnapshot(); - }); - - it('rejects invalid options even if valid options exist', () => { - expect(() => { - processOptions({ debug: true, aintnothang: true, bail: true }, 'foo'); - }).toThrow('functional_tests_server: invalid option [aintnothang]'); - }); -}); diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.js b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.js deleted file mode 100644 index d57d5c4761f6e..0000000000000 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { startServers, initLogsDir } from '../../tasks'; -import { runCli } from '../../lib'; -import { processOptions, displayHelp } from './args'; - -/** - * Start servers - * @param {string} defaultConfigPath Optional path to config - * if no config option is passed - */ -export async function startServersCli(defaultConfigPath) { - await runCli(displayHelp, async (userOptions) => { - const options = processOptions(userOptions, defaultConfigPath); - initLogsDir(options); - await startServers({ - ...options, - }); - }); -} diff --git a/packages/kbn-test/src/functional_tests/lib/__snapshots__/run_cli.test.js.snap b/packages/kbn-test/src/functional_tests/lib/__snapshots__/run_cli.test.js.snap deleted file mode 100644 index 6506675cea9bc..0000000000000 --- a/packages/kbn-test/src/functional_tests/lib/__snapshots__/run_cli.test.js.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`does right thing when non-error is thrown 1`] = ` -" -'foo bar' thrown! - ...stack trace... -" -`; - -exports[`logs no stack trace then exits when stack missing 1`] = ` -" -foo error - (no stack trace) - -" -`; - -exports[`logs the stack then exits when run function throws an error 1`] = ` -" -foo error - stack 1 - stack 2 - stack 3 - -" -`; diff --git a/packages/kbn-test/src/functional_tests/lib/index.ts b/packages/kbn-test/src/functional_tests/lib/index.ts index 8844a2ee59a19..7adf9c9d6420a 100644 --- a/packages/kbn-test/src/functional_tests/lib/index.ts +++ b/packages/kbn-test/src/functional_tests/lib/index.ts @@ -8,6 +8,4 @@ export { runKibanaServer } from './run_kibana_server'; export { runElasticsearch } from './run_elasticsearch'; -export type { CreateFtrOptions, CreateFtrParams } from './run_ftr'; -export { runFtr, hasTests, assertNoneExcluded } from './run_ftr'; -export { runCli } from './run_cli'; +export * from './run_ftr'; diff --git a/packages/kbn-test/src/functional_tests/test_helpers.ts b/packages/kbn-test/src/functional_tests/lib/logs_dir.ts similarity index 52% rename from packages/kbn-test/src/functional_tests/test_helpers.ts rename to packages/kbn-test/src/functional_tests/lib/logs_dir.ts index 4131f23770a05..0671a1ffd01e3 100644 --- a/packages/kbn-test/src/functional_tests/test_helpers.ts +++ b/packages/kbn-test/src/functional_tests/lib/logs_dir.ts @@ -6,14 +6,12 @@ * Side Public License, v 1. */ -/* eslint-env jest */ +import Path from 'path'; +import Fs from 'fs'; -import { format } from 'util'; +import { ToolingLog } from '@kbn/tooling-log'; -export function checkMockConsoleLogSnapshot(logMock: jest.Mock) { - const output = logMock.mock.calls - .reduce((acc, args) => `${acc}${format(...args)}\n`, '') - .replace(/(^ at.+[>)\d]$\n?)+/m, ' ...stack trace...'); - - expect(output).toMatchSnapshot(); +export async function initLogsDir(log: ToolingLog, logsDir: string) { + log.info(`Kibana/ES logs will be written to ${Path.relative(process.cwd(), logsDir)}/`); + Fs.mkdirSync(logsDir, { recursive: true }); } diff --git a/packages/kbn-test/src/functional_tests/lib/run_cli.test.js b/packages/kbn-test/src/functional_tests/lib/run_cli.test.js deleted file mode 100644 index eccb1405d51dc..0000000000000 --- a/packages/kbn-test/src/functional_tests/lib/run_cli.test.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { runCli } from './run_cli'; -import { checkMockConsoleLogSnapshot } from '../test_helpers'; - -const mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); -const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); - -const actualProcessArgv = process.argv; - -const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - -beforeEach(() => { - process.argv = actualProcessArgv.slice(0, 2); - jest.clearAllMocks(); -}); - -afterAll(() => { - process.argv = actualProcessArgv; -}); - -it('accepts help option even if invalid options passed', async () => { - process.argv.push('--foo', '--bar', '--help'); - - const mockGetHelpText = jest.fn().mockReturnValue('mock help text'); - const mockRun = jest.fn(); - await runCli(mockGetHelpText, mockRun); - - expect(mockProcessExit).not.toHaveBeenCalled(); - expect(mockGetHelpText).toHaveBeenCalledTimes(1); - expect(mockConsoleLog).toHaveBeenCalledTimes(1); - expect(mockConsoleLog).toHaveBeenCalledWith('mock help text'); - expect(mockRun).not.toHaveBeenCalled(); -}); - -it('passes parsed argv to run function', async () => { - process.argv.push('--foo', 'bar', '--baz=box', '--', 'a', 'b', 'c'); - - const mockGetHelpText = jest.fn(); - const mockRun = jest.fn(); - await runCli(mockGetHelpText, mockRun); - - expect(mockGetHelpText).not.toHaveBeenCalled(); - expect(mockConsoleLog).not.toHaveBeenCalled(); - expect(mockProcessExit).not.toHaveBeenCalled(); - expect(mockRun).toHaveBeenCalledTimes(1); - expect(mockRun).toHaveBeenCalledWith({ - foo: 'bar', - baz: 'box', - _: ['a', 'b', 'c'], - }); -}); - -it('waits for promise returned from run function to resolve before resolving', async () => { - let resolveMockRun; - const mockRun = jest.fn().mockImplementation( - () => - new Promise((resolve) => { - resolveMockRun = resolve; - }) - ); - - const onResolved = jest.fn(); - const promise = runCli(null, mockRun).then(onResolved); - - expect(mockRun).toHaveBeenCalled(); - expect(onResolved).not.toHaveBeenCalled(); - - await sleep(500); - - expect(onResolved).not.toHaveBeenCalled(); - - resolveMockRun(); - await promise; - expect(onResolved).toHaveBeenCalled(); -}); - -it('logs the stack then exits when run function throws an error', async () => { - await runCli(null, () => { - const error = new Error('foo error'); - error.stack = 'foo error\n stack 1\n stack 2\n stack 3'; - throw error; - }); - - expect(mockProcessExit).toHaveBeenCalledTimes(1); - expect(mockProcessExit).toHaveBeenCalledWith(1); - - expect(mockConsoleLog).toHaveBeenCalled(); - checkMockConsoleLogSnapshot(mockConsoleLog); -}); - -it('logs no stack trace then exits when stack missing', async () => { - await runCli(null, () => { - const error = new Error('foo error'); - error.stack = undefined; - throw error; - }); - - expect(mockProcessExit).toHaveBeenCalledTimes(1); - expect(mockProcessExit).toHaveBeenCalledWith(1); - - expect(mockConsoleLog).toHaveBeenCalled(); - checkMockConsoleLogSnapshot(mockConsoleLog); -}); - -it('does right thing when non-error is thrown', async () => { - await runCli(null, () => { - throw 'foo bar'; - }); - - expect(mockProcessExit).toHaveBeenCalledTimes(1); - expect(mockProcessExit).toHaveBeenCalledWith(1); - - expect(mockConsoleLog).toHaveBeenCalled(); - checkMockConsoleLogSnapshot(mockConsoleLog); -}); diff --git a/packages/kbn-test/src/functional_tests/lib/run_cli.ts b/packages/kbn-test/src/functional_tests/lib/run_cli.ts deleted file mode 100644 index 3e2cb50ff2e78..0000000000000 --- a/packages/kbn-test/src/functional_tests/lib/run_cli.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { inspect } from 'util'; - -import chalk from 'chalk'; -import getopts from 'getopts'; -/* eslint-disable no-console */ -export class CliError extends Error { - constructor(message: string) { - super(message); - Error.captureStackTrace(this, CliError); - } -} - -export async function runCli( - getHelpText: () => string, - run: (options: getopts.ParsedOptions) => Promise -) { - try { - const userOptions = getopts(process.argv.slice(2)) || {}; - if (userOptions.help) { - console.log(getHelpText()); - return; - } - - await run(userOptions); - } catch (error) { - if (!(error instanceof Error)) { - error = new Error(`${inspect(error)} thrown!`); - } - - console.log(); - console.log(chalk.red(error.message)); - - // CliError is a special error class that indicates that the error is produced as a part - // of using the CLI, and does not need a stack trace to make sense, so we skip the stack - // trace logging if the error thrown is an instance of this class - if (!(error instanceof CliError)) { - // first line in the stack trace is the message, skip it as we log it directly and color it red - if (error.stack) { - console.log(error.stack.split('\n').slice(1).join('\n')); - } else { - console.log(' (no stack trace)'); - } - } - - console.log(); - - process.exit(error.exitCode || 1); - } -} diff --git a/packages/kbn-test/src/functional_tests/lib/run_ftr.ts b/packages/kbn-test/src/functional_tests/lib/run_ftr.ts index b9945adbdfb56..665d27c00754e 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_ftr.ts +++ b/packages/kbn-test/src/functional_tests/lib/run_ftr.ts @@ -6,127 +6,45 @@ * Side Public License, v 1. */ import type { ToolingLog } from '@kbn/tooling-log'; -import { FunctionalTestRunner, readConfigFile, EsVersion } from '../../functional_test_runner'; -import { CliError } from './run_cli'; +import { createFailError } from '@kbn/dev-cli-errors'; -export interface CreateFtrOptions { - /** installation dir from which to run Kibana */ - installDir: string; +import { EsVersion, Config, FunctionalTestRunner } from '../../functional_test_runner'; + +export async function runFtr(options: { log: ToolingLog; - /** Whether to exit test run at the first failure */ - bail?: boolean; - grep: string; - updateBaselines?: boolean; - suiteFiles?: { - include?: string[]; - exclude?: string[]; - }; - suiteTags?: { - include?: string[]; - exclude?: string[]; - }; - updateSnapshots?: boolean; + config: Config; esVersion: EsVersion; - dryRun?: boolean; -} - -export interface CreateFtrParams { - configPath: string; - options: CreateFtrOptions; -} -async function createFtr({ - configPath, - options: { - installDir, - log, - bail, - grep, - updateBaselines, - suiteFiles, - suiteTags, - updateSnapshots, - esVersion, - dryRun, - }, -}: CreateFtrParams) { - const config = await readConfigFile(log, esVersion, configPath); - - return { - config, - ftr: new FunctionalTestRunner( - log, - configPath, - { - mochaOpts: { - bail: !!bail, - grep, - dryRun: !!dryRun, - }, - kbnTestServer: { - installDir, - }, - updateBaselines, - updateSnapshots, - suiteFiles: { - include: [...(suiteFiles?.include || []), ...config.get('suiteFiles.include')], - exclude: [...(suiteFiles?.exclude || []), ...config.get('suiteFiles.exclude')], - }, - suiteTags: { - include: [...(suiteTags?.include || []), ...config.get('suiteTags.include')], - exclude: [...(suiteTags?.exclude || []), ...config.get('suiteTags.exclude')], - }, - }, - esVersion - ), - }; -} - -export async function assertNoneExcluded(params: CreateFtrParams) { - const { config, ftr } = await createFtr(params); - - if (config.get('testRunner')) { - // tests with custom test runners are not included in this check - return; - } - - const stats = await ftr.getTestStats(); - if (!stats) { - throw new Error('unable to get test stats'); - } - if (stats.testsExcludedByTag.length > 0) { - throw new CliError(` - ${stats.testsExcludedByTag.length} tests in the ${params.configPath} config - are excluded when filtering by the tags run on CI. Make sure that all suites are - tagged with one of the following tags: + signal?: AbortSignal; +}) { + const ftr = new FunctionalTestRunner(options.log, options.config, options.esVersion); - ${JSON.stringify(params.options.suiteTags)} - - - ${stats.testsExcludedByTag.join('\n - ')} - `); - } -} - -export async function runFtr(params: CreateFtrParams, signal?: AbortSignal) { - const { ftr } = await createFtr(params); - - const failureCount = await ftr.run(signal); + const failureCount = await ftr.run(options.signal); if (failureCount > 0) { - throw new CliError( + throw createFailError( `${failureCount} functional test ${failureCount === 1 ? 'failure' : 'failures'}` ); } } -export async function hasTests(params: CreateFtrParams) { - const { ftr, config } = await createFtr(params); - - if (config.get('testRunner')) { +export async function checkForEnabledTestsInFtrConfig(options: { + log: ToolingLog; + config: Config; + esVersion: EsVersion; +}) { + if (options.config.get('testRunner')) { // configs with custom test runners are assumed to always have tests return true; } + + if (options.config.module.type === 'journey') { + return !options.config.module.journey.config.isSkipped(); + } + + const ftr = new FunctionalTestRunner(options.log, options.config, options.esVersion); const stats = await ftr.getTestStats(); if (!stats) { - throw new Error('unable to get test stats'); + throw createFailError('unable to get test stats'); } + return stats.nonSkippedTestCount > 0; } diff --git a/packages/kbn-test/src/functional_tests/lib/run_kibana_server.ts b/packages/kbn-test/src/functional_tests/lib/run_kibana_server.ts index 2ae15ca5f83f8..2ab4af2df2e2d 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_kibana_server.ts +++ b/packages/kbn-test/src/functional_tests/lib/run_kibana_server.ts @@ -34,25 +34,19 @@ function extendNodeOptions(installDir?: string) { }; } -export async function runKibanaServer({ - procs, - config, - options, - onEarlyExit, -}: { +export async function runKibanaServer(options: { procs: ProcRunner; config: Config; - options: { - installDir?: string; - extraKbnOpts?: string[]; - logsDir?: string; - }; + installDir?: string; + extraKbnOpts?: string[]; + logsDir?: string; onEarlyExit?: (msg: string) => void; }) { - const runOptions = config.get('kbnTestServer.runOptions'); + const { config, procs } = options; + const runOptions = options.config.get('kbnTestServer.runOptions'); const installDir = runOptions.alwaysUseSource ? undefined : options.installDir; const devMode = !installDir; - const useTaskRunner = config.get('kbnTestServer.useDedicatedTaskRunner'); + const useTaskRunner = options.config.get('kbnTestServer.useDedicatedTaskRunner'); const procRunnerOpts = { cwd: installDir || REPO_ROOT, @@ -64,11 +58,11 @@ export async function runKibanaServer({ env: { FORCE_COLOR: 1, ...process.env, - ...config.get('kbnTestServer.env'), + ...options.config.get('kbnTestServer.env'), ...extendNodeOptions(installDir), }, wait: runOptions.wait, - onEarlyExit, + onEarlyExit: options.onEarlyExit, }; const prefixArgs = devMode diff --git a/packages/kbn-test/src/functional_tests/run_tests/cli.ts b/packages/kbn-test/src/functional_tests/run_tests/cli.ts new file mode 100644 index 0000000000000..19a003dd973cf --- /dev/null +++ b/packages/kbn-test/src/functional_tests/run_tests/cli.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 { run } from '@kbn/dev-cli-runner'; + +import { initLogsDir } from '../lib/logs_dir'; +import { runTests } from './run_tests'; +import { parseFlags, FLAG_OPTIONS } from './flags'; + +export function runTestsCli() { + run( + async ({ flagsReader, log }) => { + const options = parseFlags(flagsReader); + + if (options.logsDir) { + initLogsDir(log, options.logsDir); + } + + await runTests(log, options); + }, + { + description: `Run Functional Tests`, + usage: ` + node scripts/functional_tests --help + node scripts/functional_tests [--config [--config ...]] + node scripts/functional_tests [options] [-- --] + `, + flags: FLAG_OPTIONS, + } + ); +} diff --git a/packages/kbn-test/src/functional_tests/run_tests/flags.test.ts b/packages/kbn-test/src/functional_tests/run_tests/flags.test.ts new file mode 100644 index 0000000000000..dbb8b1e9762e5 --- /dev/null +++ b/packages/kbn-test/src/functional_tests/run_tests/flags.test.ts @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createAbsolutePathSerializer, createAnyInstanceSerializer } from '@kbn/jest-serializers'; +import { FlagsReader, getFlags } from '@kbn/dev-cli-runner'; + +import { EsVersion } from '../../functional_test_runner'; +import { parseFlags, FLAG_OPTIONS } from './flags'; + +jest.mock('uuid', () => ({ v4: () => 'some-uuid' })); + +expect.addSnapshotSerializer(createAbsolutePathSerializer()); +expect.addSnapshotSerializer( + createAnyInstanceSerializer(EsVersion, (v: EsVersion) => `EsVersion ${v.toString()}`) +); + +const INITIAL_TEST_ES_FROM = process.env.TEST_ES_FROM; +beforeEach(() => { + process.env.TEST_ES_FROM = 'snapshot'; +}); +afterEach(() => { + process.env.TEST_ES_FROM = INITIAL_TEST_ES_FROM; +}); + +const defaults = getFlags(['--config=foo'], FLAG_OPTIONS); + +const test = (opts: Record) => + parseFlags(new FlagsReader({ ...defaults, ...opts })); + +describe('parse runTest flags', () => { + it('validates defaults', () => { + expect(test({})).toMatchInlineSnapshot(` + Object { + "bail": false, + "configs": Array [ + /foo, + ], + "dryRun": false, + "esFrom": "snapshot", + "esVersion": , + "grep": undefined, + "installDir": undefined, + "logsDir": undefined, + "suiteFilters": Object { + "exclude": Array [], + "include": Array [], + }, + "suiteTags": Object { + "exclude": Array [], + "include": Array [], + }, + "updateBaselines": false, + "updateSnapshots": false, + } + `); + }); + + it('allows combinations of config and journey', () => { + expect(() => test({ config: undefined })).toThrowErrorMatchingInlineSnapshot( + `"At least one --config or --journey flag is required"` + ); + + expect(test({ config: ['configFoo'], journey: 'journeyFoo' }).configs).toMatchInlineSnapshot(` + Array [ + /configFoo, + /journeyFoo, + ] + `); + + expect(test({ config: undefined, journey: 'foo' }).configs).toMatchInlineSnapshot(` + Array [ + /foo, + ] + `); + + expect(test({ config: undefined, journey: ['foo', 'bar', 'baz'] }).configs) + .toMatchInlineSnapshot(` + Array [ + /foo, + /bar, + /baz, + ] + `); + + expect(test({ config: ['bar'], journey: ['foo', 'baz'] }).configs).toMatchInlineSnapshot(` + Array [ + /bar, + /foo, + /baz, + ] + `); + }); + + it('updates all with updateAll', () => { + const { updateBaselines, updateSnapshots } = test({ updateAll: true }); + expect({ updateBaselines, updateSnapshots }).toMatchInlineSnapshot(` + Object { + "updateBaselines": true, + "updateSnapshots": true, + } + `); + }); + + it('validates esFrom', () => { + expect(() => test({ esFrom: 'foo' })).toThrowErrorMatchingInlineSnapshot( + `"invalid --esFrom, expected one of \\"snapshot\\", \\"source\\""` + ); + }); + + it('accepts multiple tags', () => { + const { suiteFilters, suiteTags } = test({ + 'include-tag': ['foo', 'bar'], + include: 'path', + exclude: ['foo'], + 'exclude-tag': ['foo'], + }); + + expect({ suiteFilters, suiteTags }).toMatchInlineSnapshot(` + Object { + "suiteFilters": Object { + "exclude": Array [ + /foo, + ], + "include": Array [ + /path, + ], + }, + "suiteTags": Object { + "exclude": Array [ + "foo", + ], + "include": Array [ + "foo", + "bar", + ], + }, + } + `); + }); +}); + +it('supports logsDir', () => { + expect(test({ logToFile: true }).logsDir).toMatchInlineSnapshot( + `/data/ftr_servers_logs/some-uuid` + ); +}); diff --git a/packages/kbn-test/src/functional_tests/run_tests/flags.ts b/packages/kbn-test/src/functional_tests/run_tests/flags.ts new file mode 100644 index 0000000000000..7639ae341f071 --- /dev/null +++ b/packages/kbn-test/src/functional_tests/run_tests/flags.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 Path from 'path'; + +import { v4 as uuidV4 } from 'uuid'; +import { REPO_ROOT } from '@kbn/utils'; +import { FlagsReader, FlagOptions } from '@kbn/dev-cli-runner'; +import { createFlagError } from '@kbn/dev-cli-errors'; + +import { EsVersion } from '../../functional_test_runner'; + +export type RunTestsOptions = ReturnType; + +export const FLAG_OPTIONS: FlagOptions = { + boolean: ['bail', 'logToFile', 'dry-run', 'updateBaselines', 'updateSnapshots', 'updateAll'], + string: [ + 'config', + 'journey', + 'esFrom', + 'kibana-install-dir', + 'grep', + 'include-tag', + 'exclude-tag', + 'include', + 'exclude', + ], + alias: { + updateAll: 'u', + }, + help: ` + --config Define a FTR config that should be executed. Can be specified multiple times + --journey Define a Journey that should be executed. Can be specified multiple times + --esFrom Build Elasticsearch from source or run from snapshot. Default: $TEST_ES_FROM or "snapshot" + --include-tag Tags that suites must include to be run, can be included multiple times + --exclude-tag Tags that suites must NOT include to be run, can be included multiple times + --include Files that must included to be run, can be included multiple times + --exclude Files that must NOT be included to be run, can be included multiple times + --grep Pattern to select which tests to run + --kibana-install-dir Run Kibana from existing install directory instead of from source + --bail Stop the test run at the first failure + --logToFile Write the log output from Kibana/ES to files instead of to stdout + --dry-run Report tests without executing them + --updateBaselines Replace baseline screenshots with whatever is generated from the test + --updateSnapshots Replace inline and file snapshots with whatever is generated from the test + --updateAll, -u Replace both baseline screenshots and snapshots + `, +}; + +export function parseFlags(flags: FlagsReader) { + const configs = [ + ...(flags.arrayOfPaths('config') ?? []), + ...(flags.arrayOfPaths('journey') ?? []), + ]; + + if (!configs.length) { + throw createFlagError('At least one --config or --journey flag is required'); + } + + const esVersionString = flags.string('es-version'); + + return { + configs, + esVersion: esVersionString ? new EsVersion(esVersionString) : EsVersion.getDefault(), + bail: flags.boolean('bail'), + dryRun: flags.boolean('dry-run'), + updateBaselines: flags.boolean('updateBaselines') || flags.boolean('updateAll'), + updateSnapshots: flags.boolean('updateSnapshots') || flags.boolean('updateAll'), + logsDir: flags.boolean('logToFile') + ? Path.resolve(REPO_ROOT, 'data/ftr_servers_logs', uuidV4()) + : undefined, + esFrom: flags.enum('esFrom', ['snapshot', 'source']) ?? 'snapshot', + installDir: flags.path('kibana-install-dir'), + grep: flags.string('grep'), + suiteTags: { + include: flags.arrayOfStrings('include-tag'), + exclude: flags.arrayOfStrings('exclude-tag'), + }, + suiteFilters: { + include: flags.arrayOfPaths('include'), + exclude: flags.arrayOfPaths('exclude'), + }, + }; +} diff --git a/packages/kbn-test/src/functional_tests/run_tests/index.ts b/packages/kbn-test/src/functional_tests/run_tests/index.ts new file mode 100644 index 0000000000000..6b0ed25db6849 --- /dev/null +++ b/packages/kbn-test/src/functional_tests/run_tests/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 { runTestsCli } from './cli'; +export { runTests } from './run_tests'; diff --git a/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts b/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts new file mode 100644 index 0000000000000..3eb8348691a1b --- /dev/null +++ b/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import { setTimeout } from 'timers/promises'; + +import { REPO_ROOT } from '@kbn/utils'; +import { ToolingLog } from '@kbn/tooling-log'; +import { withProcRunner } from '@kbn/dev-proc-runner'; + +import { readConfigFile } from '../../functional_test_runner'; + +import { checkForEnabledTestsInFtrConfig, runFtr } from '../lib/run_ftr'; +import { runElasticsearch } from '../lib/run_elasticsearch'; +import { runKibanaServer } from '../lib/run_kibana_server'; +import { RunTestsOptions } from './flags'; + +/** + * Run servers and tests for each config + */ +export async function runTests(log: ToolingLog, options: RunTestsOptions) { + if (!process.env.CI) { + log.warning('❗️❗️❗️'); + log.warning('❗️❗️❗️'); + log.warning('❗️❗️❗️'); + log.warning( + " Don't forget to use `node scripts/build_kibana_platform_plugins` to build plugins you plan on testing" + ); + log.warning('❗️❗️❗️'); + log.warning('❗️❗️❗️'); + log.warning('❗️❗️❗️'); + } + + for (const [i, path] of options.configs.entries()) { + await log.indent(0, async () => { + if (options.configs.length > 1) { + const progress = `${i + 1}/${options.configs.length}`; + log.write(`--- [${progress}] Running ${Path.relative(REPO_ROOT, path)}`); + } + + const config = await readConfigFile(log, options.esVersion, path); + + const hasTests = await checkForEnabledTestsInFtrConfig({ + config, + esVersion: options.esVersion, + log, + }); + if (!hasTests) { + // just run the FTR, no Kibana or ES, which will quickly report a skipped test group to ci-stats and continue + await runFtr({ + log, + config, + esVersion: options.esVersion, + }); + return; + } + + await withProcRunner(log, async (procs) => { + const abortCtrl = new AbortController(); + + const onEarlyExit = (msg: string) => { + log.error(msg); + abortCtrl.abort(); + }; + + let shutdownEs; + try { + if (process.env.TEST_ES_DISABLE_STARTUP !== 'true') { + shutdownEs = await runElasticsearch({ ...options, log, config, onEarlyExit }); + if (abortCtrl.signal.aborted) { + return; + } + } + + await runKibanaServer({ + procs, + config, + logsDir: options.logsDir, + installDir: options.installDir, + onEarlyExit, + }); + + if (abortCtrl.signal.aborted) { + return; + } + + await runFtr({ + log, + config, + esVersion: options.esVersion, + signal: abortCtrl.signal, + }); + } finally { + try { + const delay = config.get('kbnTestServer.delayShutdown'); + if (typeof delay === 'number') { + log.info('Delaying shutdown of Kibana for', delay, 'ms'); + await setTimeout(delay); + } + + await procs.stop('kibana'); + } finally { + if (shutdownEs) { + await shutdownEs(); + } + } + } + }); + }); + } +} diff --git a/packages/kbn-test/src/functional_tests/start_servers/cli.ts b/packages/kbn-test/src/functional_tests/start_servers/cli.ts new file mode 100644 index 0000000000000..548e0310d5bcc --- /dev/null +++ b/packages/kbn-test/src/functional_tests/start_servers/cli.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 { run } from '@kbn/dev-cli-runner'; + +import { initLogsDir } from '../lib/logs_dir'; + +import { parseFlags, FLAG_OPTIONS } from './flags'; +import { startServers } from './start_servers'; + +/** + * Start servers + */ +export function startServersCli() { + run( + async ({ flagsReader: flags, log }) => { + const options = parseFlags(flags); + + if (options.logsDir) { + initLogsDir(log, options.logsDir); + } + + await startServers(log, options); + }, + { + flags: FLAG_OPTIONS, + } + ); +} diff --git a/packages/kbn-test/src/functional_tests/start_servers/flags.test.ts b/packages/kbn-test/src/functional_tests/start_servers/flags.test.ts new file mode 100644 index 0000000000000..5f40b2ae66828 --- /dev/null +++ b/packages/kbn-test/src/functional_tests/start_servers/flags.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 { getFlags, FlagsReader } from '@kbn/dev-cli-runner'; +import { createAnyInstanceSerializer, createAbsolutePathSerializer } from '@kbn/jest-serializers'; +import { EsVersion } from '../../functional_test_runner'; +import { parseFlags, FLAG_OPTIONS } from './flags'; + +jest.mock('uuid', () => ({ v4: () => 'some-uuid' })); + +expect.addSnapshotSerializer( + createAnyInstanceSerializer(EsVersion, (v: EsVersion) => `EsVersion ${v.toString()}`) +); +expect.addSnapshotSerializer(createAbsolutePathSerializer()); + +const defaults = getFlags(['--config=foo'], FLAG_OPTIONS); + +const test = (opts: Record) => + parseFlags(new FlagsReader({ ...defaults, ...opts })); + +it('parses a subset of the flags from runTests', () => { + expect(test({ config: 'foo' })).toMatchInlineSnapshot(` + Object { + "config": "foo", + "esFrom": undefined, + "esVersion": , + "installDir": undefined, + "logsDir": undefined, + } + `); +}); + +it('rejects zero configs', () => { + expect(() => test({ config: [] })).toThrowErrorMatchingInlineSnapshot( + `"expected exactly one --config or --journey flag"` + ); +}); + +it('rejects two configs', () => { + expect(() => test({ config: ['foo'], journey: ['bar'] })).toThrowErrorMatchingInlineSnapshot( + `"expected exactly one --config or --journey flag"` + ); +}); + +it('supports logsDir', () => { + expect(test({ logToFile: true }).logsDir).toMatchInlineSnapshot( + `/data/ftr_servers_logs/some-uuid` + ); +}); diff --git a/packages/kbn-test/src/functional_tests/start_servers/flags.ts b/packages/kbn-test/src/functional_tests/start_servers/flags.ts new file mode 100644 index 0000000000000..8ce3af9f5917b --- /dev/null +++ b/packages/kbn-test/src/functional_tests/start_servers/flags.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 Path from 'path'; + +import { v4 as uuidV4 } from 'uuid'; +import { FlagsReader, FlagOptions } from '@kbn/dev-cli-runner'; +import { createFlagError } from '@kbn/dev-cli-errors'; +import { REPO_ROOT } from '@kbn/utils'; + +import { EsVersion } from '../../functional_test_runner'; + +export type StartServerOptions = ReturnType; + +export const FLAG_OPTIONS: FlagOptions = { + string: ['config', 'journey', 'esFrom', 'kibana-install-dir'], + boolean: ['logToFile'], + help: ` + --config Define a FTR config that should be executed. Can be specified multiple times + --journey Define a Journey that should be executed. Can be specified multiple times + --esFrom Build Elasticsearch from source or run from snapshot. Default: $TEST_ES_FROM or "snapshot" + --kibana-install-dir Run Kibana from existing install directory instead of from source + --logToFile Write the log output from Kibana/ES to files instead of to stdout + `, +}; + +export function parseFlags(flags: FlagsReader) { + const configs = [ + ...(flags.arrayOfStrings('config') ?? []), + ...(flags.arrayOfStrings('journey') ?? []), + ]; + if (configs.length !== 1) { + throw createFlagError(`expected exactly one --config or --journey flag`); + } + + return { + config: configs[0], + esFrom: flags.enum('esFrom', ['source', 'snapshot']), + esVersion: EsVersion.getDefault(), + installDir: flags.string('kibana-install-dir'), + logsDir: flags.boolean('logToFile') + ? Path.resolve(REPO_ROOT, 'data/ftr_servers_logs', uuidV4()) + : undefined, + }; +} diff --git a/packages/kbn-test/src/functional_tests/start_servers/index.ts b/packages/kbn-test/src/functional_tests/start_servers/index.ts new file mode 100644 index 0000000000000..ff88a1f4cb476 --- /dev/null +++ b/packages/kbn-test/src/functional_tests/start_servers/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 { startServersCli } from './cli'; +export { startServers } from './start_servers'; diff --git a/packages/kbn-test/src/functional_tests/start_servers/start_servers.ts b/packages/kbn-test/src/functional_tests/start_servers/start_servers.ts new file mode 100644 index 0000000000000..3bb601fabe002 --- /dev/null +++ b/packages/kbn-test/src/functional_tests/start_servers/start_servers.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 Path from 'path'; + +import * as Rx from 'rxjs'; +import dedent from 'dedent'; +import { REPO_ROOT } from '@kbn/utils'; +import { ToolingLog } from '@kbn/tooling-log'; +import { withProcRunner } from '@kbn/dev-proc-runner'; +import { getTimeReporter } from '@kbn/ci-stats-reporter'; + +import { readConfigFile } from '../../functional_test_runner'; +import { runElasticsearch } from '../lib/run_elasticsearch'; +import { runKibanaServer } from '../lib/run_kibana_server'; +import { StartServerOptions } from './flags'; + +const FTR_SCRIPT_PATH = Path.resolve(REPO_ROOT, 'scripts/functional_test_runner'); + +export async function startServers(log: ToolingLog, options: StartServerOptions) { + const runStartTime = Date.now(); + const reportTime = getTimeReporter(log, 'scripts/functional_tests_server'); + + await withProcRunner(log, async (procs) => { + const config = await readConfigFile(log, options.esVersion, options.config); + + const shutdownEs = await runElasticsearch({ + config, + log, + esFrom: options.esFrom, + logsDir: options.logsDir, + }); + + await runKibanaServer({ + procs, + config, + installDir: options.installDir, + extraKbnOpts: options.installDir ? [] : ['--dev', '--no-dev-config', '--no-dev-credentials'], + }); + + reportTime(runStartTime, 'ready', { + success: true, + ...options, + }); + + // wait for 5 seconds of silence before logging the + // success message so that it doesn't get buried + await silence(log, 5000); + + const installDirFlag = options.installDir ? ` --kibana-install-dir=${options.installDir}` : ''; + const rel = Path.relative(process.cwd(), config.module.path); + const pathsMessage = ` --${config.module.type}=${rel}`; + + log.success( + '\n\n' + + dedent` + Elasticsearch and Kibana are ready for functional testing. Start the functional tests + in another terminal session by running this command from this directory: + + node ${Path.relative(process.cwd(), FTR_SCRIPT_PATH)}${installDirFlag}${pathsMessage} + ` + + '\n\n' + ); + + await procs.waitForAllToStop(); + await shutdownEs(); + }); +} + +async function silence(log: ToolingLog, milliseconds: number) { + await Rx.firstValueFrom( + log.getWritten$().pipe( + Rx.startWith(null), + Rx.switchMap(() => Rx.timer(milliseconds)) + ) + ); +} diff --git a/packages/kbn-test/src/functional_tests/tasks.ts b/packages/kbn-test/src/functional_tests/tasks.ts deleted file mode 100644 index 26504b07544b0..0000000000000 --- a/packages/kbn-test/src/functional_tests/tasks.ts +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Fs from 'fs'; -import Path from 'path'; -import { setTimeout } from 'timers/promises'; - -import * as Rx from 'rxjs'; -import { startWith, switchMap, take } from 'rxjs/operators'; -import { withProcRunner } from '@kbn/dev-proc-runner'; -import { ToolingLog } from '@kbn/tooling-log'; -import { getTimeReporter } from '@kbn/ci-stats-reporter'; -import { REPO_ROOT } from '@kbn/utils'; -import dedent from 'dedent'; - -import { readConfigFile, EsVersion } from '../functional_test_runner/lib'; -import { - runElasticsearch, - runKibanaServer, - runFtr, - assertNoneExcluded, - hasTests, - CreateFtrOptions, -} from './lib'; - -const FTR_SCRIPT_PATH = Path.resolve(REPO_ROOT, 'scripts/functional_test_runner'); - -const makeSuccessMessage = (options: StartServerOptions) => { - const installDirFlag = options.installDir ? ` --kibana-install-dir=${options.installDir}` : ''; - const configPaths: string[] = Array.isArray(options.config) ? options.config : [options.config]; - const pathsMessage = options.useDefaultConfig - ? '' - : configPaths - .map((path) => Path.relative(process.cwd(), path)) - .map((path) => ` --config ${path}`) - .join(''); - - return ( - '\n\n' + - dedent` - Elasticsearch and Kibana are ready for functional testing. Start the functional tests - in another terminal session by running this command from this directory: - - node ${Path.relative(process.cwd(), FTR_SCRIPT_PATH)}${installDirFlag}${pathsMessage} - ` + - '\n\n' - ); -}; - -export async function initLogsDir(options: { logsDir?: string; createLogger(): ToolingLog }) { - if (options.logsDir) { - options - .createLogger() - .info(`Kibana/ES logs will be written to ${Path.relative(process.cwd(), options.logsDir)}/`); - - Fs.mkdirSync(options.logsDir, { recursive: true }); - } -} - -/** - * Run servers and tests for each config - */ -interface RunTestsParams extends CreateFtrOptions { - /** Array of paths to configs */ - configs: string[]; - /** run from source instead of snapshot */ - esFrom?: string; - esVersion: EsVersion; - createLogger: () => ToolingLog; - extraKbnOpts: string[]; - assertNoneExcluded: boolean; -} -export async function runTests(options: RunTestsParams) { - if (!process.env.CI && !options.assertNoneExcluded) { - const log = options.createLogger(); - log.warning('❗️❗️❗️'); - log.warning('❗️❗️❗️'); - log.warning('❗️❗️❗️'); - log.warning( - " Don't forget to use `node scripts/build_kibana_platform_plugins` to build plugins you plan on testing" - ); - log.warning('❗️❗️❗️'); - log.warning('❗️❗️❗️'); - log.warning('❗️❗️❗️'); - } - - const log = options.createLogger(); - - if (options.assertNoneExcluded) { - log.write('--- asserting that all tests belong to a ciGroup'); - for (const configPath of options.configs) { - log.info('loading', configPath); - await log.indent(4, async () => { - await assertNoneExcluded({ configPath, options: { ...options, log } }); - }); - continue; - } - - return; - } - - for (const [i, configPath] of options.configs.entries()) { - await log.indent(0, async () => { - if (options.configs.length > 1) { - const progress = `${i + 1}/${options.configs.length}`; - log.write(`--- [${progress}] Running ${Path.relative(REPO_ROOT, configPath)}`); - } - - if (!(await hasTests({ configPath, options: { ...options, log } }))) { - // just run the FTR, no Kibana or ES, which will quickly report a skipped test group to ci-stats and continue - await runFtr({ configPath, options: { ...options, log } }); - return; - } - - await withProcRunner(log, async (procs) => { - const config = await readConfigFile(log, options.esVersion, configPath); - const abortCtrl = new AbortController(); - - const onEarlyExit = (msg: string) => { - log.error(msg); - abortCtrl.abort(); - }; - - let shutdownEs; - try { - if (process.env.TEST_ES_DISABLE_STARTUP !== 'true') { - shutdownEs = await runElasticsearch({ ...options, log, config, onEarlyExit }); - if (abortCtrl.signal.aborted) { - return; - } - } - await runKibanaServer({ procs, config, options, onEarlyExit }); - if (abortCtrl.signal.aborted) { - return; - } - await runFtr({ configPath, options: { ...options, log } }, abortCtrl.signal); - } finally { - try { - const delay = config.get('kbnTestServer.delayShutdown'); - if (typeof delay === 'number') { - log.info('Delaying shutdown of Kibana for', delay, 'ms'); - await setTimeout(delay); - } - - await procs.stop('kibana'); - } finally { - if (shutdownEs) { - await shutdownEs(); - } - } - } - }); - }); - } -} - -interface StartServerOptions { - /** Path to a config file */ - config: string; - log: ToolingLog; - /** installation dir from which to run Kibana */ - installDir?: string; - /** run from source instead of snapshot */ - esFrom?: string; - createLogger: () => ToolingLog; - extraKbnOpts: string[]; - useDefaultConfig?: boolean; - esVersion: EsVersion; -} - -export async function startServers({ ...options }: StartServerOptions) { - const runStartTime = Date.now(); - const toolingLog = new ToolingLog({ - level: 'info', - writeTo: process.stdout, - }); - const reportTime = getTimeReporter(toolingLog, 'scripts/functional_tests_server'); - - const log = options.createLogger(); - const opts = { - ...options, - log, - }; - - await withProcRunner(log, async (procs) => { - const config = await readConfigFile(log, options.esVersion, options.config); - - const shutdownEs = await runElasticsearch({ ...opts, config }); - await runKibanaServer({ - procs, - config, - options: { - ...opts, - extraKbnOpts: [ - ...options.extraKbnOpts, - ...(options.installDir ? [] : ['--dev', '--no-dev-config', '--no-dev-credentials']), - ], - }, - }); - - reportTime(runStartTime, 'ready', { - success: true, - ...options, - }); - - // wait for 5 seconds of silence before logging the - // success message so that it doesn't get buried - await silence(log, 5000); - log.success(makeSuccessMessage(options)); - - await procs.waitForAllToStop(); - await shutdownEs(); - }); -} - -async function silence(log: ToolingLog, milliseconds: number) { - await log - .getWritten$() - .pipe( - startWith(null), - switchMap(() => Rx.timer(milliseconds)), - take(1) - ) - .toPromise(); -} diff --git a/packages/kbn-test/src/mocha/junit_report_generation.js b/packages/kbn-test/src/mocha/junit_report_generation.js index 98210aba770b7..599d1f366194f 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.js @@ -24,6 +24,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { reportName = 'Unnamed Mocha Tests', rootDirectory = REPO_ROOT, getTestMetadata = () => ({}), + metadata, } = options; const stats = {}; @@ -104,6 +105,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { tests: allTests.length + failedHooks.length, failures: failures.length, skipped: skippedResults.length, + 'metadata-json': JSON.stringify(metadata ?? {}), }); function addTestcaseEl(node) { diff --git a/packages/kbn-test/src/mocha/junit_report_generation.test.js b/packages/kbn-test/src/mocha/junit_report_generation.test.js index c19550349fd85..ac23d91390ed9 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.test.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.test.js @@ -60,6 +60,7 @@ describe('dev/mocha/junit report generation', () => { name: 'test', skipped: '1', tests: '4', + 'metadata-json': '{}', time: testsuite.$.time, timestamp: testsuite.$.timestamp, }, diff --git a/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc b/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc new file mode 100644 index 0000000000000..1fab1b9cb7d84 --- /dev/null +++ b/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-avatar-user-profile-components", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/packages/shared-ux/page/solution_nav/src/__snapshots__/solution_nav.test.tsx.snap b/packages/shared-ux/page/solution_nav/src/__snapshots__/solution_nav.test.tsx.snap index 466d3bcf0dea6..1db22e47d0c52 100644 --- a/packages/shared-ux/page/solution_nav/src/__snapshots__/solution_nav.test.tsx.snap +++ b/packages/shared-ux/page/solution_nav/src/__snapshots__/solution_nav.test.tsx.snap @@ -4,7 +4,7 @@ exports[`SolutionNav accepts EuiSideNavProps 1`] = ` +
+ +

+ + + +

+
+ + +
+
`; @@ -104,7 +193,7 @@ exports[`SolutionNav accepts canBeCollapsed prop 1`] = ` +
+ +

+ + + +

+
+ + +
+
`; @@ -295,6 +472,91 @@ exports[`SolutionNav accepts canBeCollapsed prop 2`] = ` /> +
+ +

+ + + +

+
+ + +
`; @@ -302,7 +564,7 @@ exports[`SolutionNav heading accepts more headingProps 1`] = ` +
+ +

+ + + +

+
+ +
+
`; @@ -341,7 +632,7 @@ exports[`SolutionNav renders 1`] = ` +
+ +

+ + + +

+
+ + +
+
`; @@ -440,7 +819,7 @@ exports[`SolutionNav renders with icon 1`] = ` +
+ +

+ + + + +

+
+ + +
+
`; diff --git a/packages/shared-ux/page/solution_nav/src/solution_nav.tsx b/packages/shared-ux/page/solution_nav/src/solution_nav.tsx index 31950f716c225..d6b9a2063e609 100644 --- a/packages/shared-ux/page/solution_nav/src/solution_nav.tsx +++ b/packages/shared-ux/page/solution_nav/src/solution_nav.tsx @@ -22,6 +22,7 @@ import { EuiTitle, htmlIdGenerator, useIsWithinBreakpoints, + useIsWithinMinBreakpoint, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -100,7 +101,7 @@ export const SolutionNav: FC = ({ }) => { const isSmallerBreakpoint = useIsWithinBreakpoints(mobileBreakpoints); const isMediumBreakpoint = useIsWithinBreakpoints(['m']); - const isLargerBreakpoint = useIsWithinBreakpoints(['l', 'xl']); + const isLargerBreakpoint = useIsWithinMinBreakpoint('l'); // This is used for both the `EuiSideNav` and `EuiFlyout` toggling const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false); diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx index ef94ed89ac873..a618f6d6ba41c 100644 --- a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx +++ b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx @@ -8,7 +8,12 @@ import React, { ComponentType, ReactNode, useState } from 'react'; import classNames from 'classnames'; -import { useIsWithinBreakpoints, useEuiTheme, EuiPageSidebarProps } from '@elastic/eui'; +import { + useIsWithinBreakpoints, + useEuiTheme, + useIsWithinMinBreakpoint, + EuiPageSidebarProps, +} from '@elastic/eui'; import { SolutionNav, SolutionNavProps } from './solution_nav'; import './with_solution_nav.scss'; @@ -35,7 +40,7 @@ const SOLUTION_NAV_COLLAPSED_KEY = 'solutionNavIsCollapsed'; export const withSolutionNav =

(WrappedComponent: ComponentType

) => { const WithSolutionNav = (props: Props

) => { const isMediumBreakpoint = useIsWithinBreakpoints(['m']); - const isLargerBreakpoint = useIsWithinBreakpoints(['l', 'xl']); + const isLargerBreakpoint = useIsWithinMinBreakpoint('l'); const [isSideNavOpenOnDesktop, setisSideNavOpenOnDesktop] = useState( !JSON.parse(String(localStorage.getItem(SOLUTION_NAV_COLLAPSED_KEY))) ); diff --git a/packages/shared-ux/router/impl/kibana.jsonc b/packages/shared-ux/router/impl/kibana.jsonc new file mode 100644 index 0000000000000..77f3eca900702 --- /dev/null +++ b/packages/shared-ux/router/impl/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-router", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/packages/shared-ux/router/mocks/kibana.jsonc b/packages/shared-ux/router/mocks/kibana.jsonc new file mode 100644 index 0000000000000..8f3aef23a2081 --- /dev/null +++ b/packages/shared-ux/router/mocks/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-router-mocks", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/packages/shared-ux/router/types/kibana.jsonc b/packages/shared-ux/router/types/kibana.jsonc new file mode 100644 index 0000000000000..4e328b93d6081 --- /dev/null +++ b/packages/shared-ux/router/types/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/shared-ux-router-types", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/renovate.json b/renovate.json index 508cdcc684fc2..6eed31366c409 100644 --- a/renovate.json +++ b/renovate.json @@ -216,6 +216,15 @@ "labels": ["release_note:skip", "backport:skip"], "enabled": true, "prCreation": "immediate" + }, + { + "groupName": "TTY Output", + "matchPackageNames": ["xterm", "byte-size", "@types/byte-size"], + "reviewers": ["team:awp-viz"], + "matchBaseBranches": ["main"], + "labels": ["Team: AWP: Visualization", "release_note:skip", "backport:skip"], + "enabled": true, + "prCreation": "immediate" } ] } diff --git a/scripts/functional_tests_server.js b/scripts/functional_tests_server.js index 836a1ede126e3..1d51041dccd45 100644 --- a/scripts/functional_tests_server.js +++ b/scripts/functional_tests_server.js @@ -7,4 +7,4 @@ */ require('../src/setup_node_env'); -require('@kbn/test').startServersCli(require.resolve('../test/functional/config.base.js')); +require('@kbn/test').startServersCli(); diff --git a/scripts/report_failed_tests.js b/scripts/report_failed_tests.js index 3d69999a2bb1e..a56675523bba3 100644 --- a/scripts/report_failed_tests.js +++ b/scripts/report_failed_tests.js @@ -7,4 +7,4 @@ */ require('../src/setup_node_env'); -require('@kbn/test').runFailedTestsReporterCli(); +require('@kbn/failed-test-reporter-cli'); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 67c29f0fb747c..6e72b1d2623cf 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -338,6 +338,9 @@ export type { SavedObjectsFindOptions, SavedObjectsFindOptionsReference, SavedObjectsPitParams, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, + SavedObjectsBulkDeleteResponse, } from '@kbn/core-saved-objects-api-server'; export type { SavedObjectsServiceSetup, diff --git a/src/core/server/integration_tests/saved_objects/routes/bulk_delete.test.ts b/src/core/server/integration_tests/saved_objects/routes/bulk_delete.test.ts new file mode 100644 index 0000000000000..2536915f6f068 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/bulk_delete.test.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 supertest from 'supertest'; +import { savedObjectsClientMock } from '../../../mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { setupServer } from './test_utils'; +import { + registerBulkDeleteRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; + +type SetupServerReturn = Awaited>; + +describe('POST /api/saved_objects/_bulk_delete', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + savedObjectsClient = handlerContext.savedObjects.client; + + savedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [], + }); + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkDelete.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerBulkDeleteRoute(router, { coreUsageData }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('formats successful response and records usage stats', async () => { + const clientResponse = { + statuses: [ + { + id: 'abc123', + type: 'index-pattern', + success: true, + }, + ], + }; + savedObjectsClient.bulkDelete.mockImplementation(() => Promise.resolve(clientResponse)); + + const result = await supertest(httpSetup.server.listener) + .post('/api/saved_objects/_bulk_delete') + .send([ + { + id: 'abc123', + type: 'index-pattern', + }, + ]) + .expect(200); + + expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsBulkDelete).toHaveBeenCalledWith({ + request: expect.anything(), + }); + }); + + it('calls upon savedObjectClient.bulkDelete with query options', async () => { + const docs = [ + { + id: 'abc123', + type: 'index-pattern', + }, + ]; + + await supertest(httpSetup.server.listener) + .post('/api/saved_objects/_bulk_delete') + .send(docs) + .query({ force: true }) + .expect(200); + + expect(savedObjectsClient.bulkDelete).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.bulkDelete).toHaveBeenCalledWith(docs, { force: true }); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts index 7581e5f3639a2..f0fdc609d8915 100644 --- a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts +++ b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts @@ -54,6 +54,17 @@ const registerSOTypes = (setup: InternalCoreSetup) => { }, namespaceType: 'single', }); + setup.savedObjects.registerType({ + name: 'my_bulk_delete_type', + hidden: false, + mappings: { + dynamic: false, + properties: { + title: { type: 'text' }, + }, + }, + namespaceType: 'single', + }); }; describe('404s from proxies', () => { @@ -124,6 +135,7 @@ describe('404s from proxies', () => { let repository: ISavedObjectsRepository; let myOtherType: SavedObject; const myOtherTypeDocs: SavedObject[] = []; + const myBulkDeleteTypeDocs: SavedObject[] = []; beforeAll(async () => { repository = start.savedObjects.createInternalRepository(); @@ -145,6 +157,19 @@ describe('404s from proxies', () => { overwrite: true, namespace: 'default', }); + + for (let i = 1; i < 11; i++) { + myBulkDeleteTypeDocs.push({ + type: 'my_bulk_delete_type', + id: `myOtherTypeId${i}`, + attributes: { title: `MyOtherTypeTitle${i}` }, + references: [], + }); + } + await repository.bulkCreate(myBulkDeleteTypeDocs, { + overwrite: true, + namespace: 'default', + }); }); beforeEach(() => { @@ -237,6 +262,18 @@ describe('404s from proxies', () => { ); }); + it('handles `bulkDelete` requests that are successful when the proxy passes through the product header', async () => { + const docsToDelete = myBulkDeleteTypeDocs; + const bulkDeleteDocs = docsToDelete.map((doc) => ({ + id: doc.id, + type: 'my_bulk_delete_type', + })); + + const docsFound = await repository.bulkDelete(bulkDeleteDocs, { force: false }); + expect(docsFound.statuses.length).toBeGreaterThan(0); + expect(docsFound.statuses[0].success).toBe(true); + }); + it('handles `bulkGet` requests that are successful when the proxy passes through the product header', async () => { const docsToGet = myOtherTypeDocs; const docsFound = await repository.bulkGet( diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts index 6f1c2c523226c..251c7608b6299 100644 --- a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts +++ b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts @@ -122,7 +122,8 @@ export const declarePostMgetRoute = (hapiServer: Hapi.Server, hostname: string, if ( proxyInterrupt === 'bulkGetMyType' || proxyInterrupt === 'checkConficts' || - proxyInterrupt === 'internalBulkResolve' + proxyInterrupt === 'internalBulkResolve' || + proxyInterrupt === 'bulkDeleteMyDocs' ) { return proxyResponseHandler(h, hostname, port); } else { diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index fb7416baf0d03..de299c1298f9f 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -84,6 +84,6 @@ 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.3.3': ['Elastic License 2.0'], - '@elastic/eui@63.0.6': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@64.0.5': ['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 }; diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index 5f34bef2df51a..f57854b83550d 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -30,6 +30,7 @@ export const PROJECTS = [ createProject('tsconfig.json'), createProject('test/tsconfig.json', { name: 'kibana/test' }), createProject('x-pack/test/tsconfig.json', { name: 'x-pack/test' }), + createProject('x-pack/performance/tsconfig.json', { name: 'x-pack/performance' }), createProject('src/core/tsconfig.json'), createProject('.buildkite/tsconfig.json'), createProject('kbn_pm/tsconfig.json'), @@ -89,6 +90,7 @@ export const PROJECTS = [ 'src/plugins/chart_expressions/*/tsconfig.json', 'src/plugins/vis_types/*/tsconfig.json', 'x-pack/plugins/*/tsconfig.json', + 'x-pack/plugins/cloud_integrations/*/tsconfig.json', 'examples/*/tsconfig.json', 'x-pack/examples/*/tsconfig.json', 'test/analytics/fixtures/plugins/*/tsconfig.json', diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.test.tsx b/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.test.tsx index edd1acc314e8e..ab5b340012125 100644 --- a/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.test.tsx +++ b/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.test.tsx @@ -14,6 +14,7 @@ import { LayoutDirection, Metric, MetricElementEvent, + MetricWNumber, MetricWProgress, Settings, } from '@elastic/charts'; @@ -1124,7 +1125,7 @@ describe('MetricVisComponent', function () { value: primaryMetric, valueFormatter, extra, - } = component.find(Metric).props().data?.[0][0]!; + } = component.find(Metric).props().data?.[0][0]! as MetricWNumber; return { primary: valueFormatter(primaryMetric), secondary: extra?.props.children[1] }; }; diff --git a/src/plugins/chart_expressions/expression_xy/common/constants.ts b/src/plugins/chart_expressions/expression_xy/common/constants.ts index 8116f0146f19a..1c15be76bc509 100644 --- a/src/plugins/chart_expressions/expression_xy/common/constants.ts +++ b/src/plugins/chart_expressions/expression_xy/common/constants.ts @@ -123,6 +123,7 @@ export const AvailableReferenceLineIcons = { MAP_MARKER: 'mapMarker', PIN_FILLED: 'pinFilled', STAR_EMPTY: 'starEmpty', + STAR_FILLED: 'starFilled', TAG: 'tag', TRIANGLE: 'triangle', } as const; diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/icon.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/icon.ts index a74d4196f487d..652dbe168f58b 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/icon.ts +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/icon.ts @@ -94,6 +94,12 @@ export const iconSet = [ value: AvailableReferenceLineIcons.STAR_EMPTY, label: i18n.translate('expressionXY.xyChart.iconSelect.starLabel', { defaultMessage: 'Star' }), }, + { + value: AvailableReferenceLineIcons.STAR_FILLED, + label: i18n.translate('expressionXY.xyChart.iconSelect.starFilledLabel', { + defaultMessage: 'Star filled', + }), + }, { value: AvailableReferenceLineIcons.TAG, label: i18n.translate('expressionXY.xyChart.iconSelect.tagIconLabel', { diff --git a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts index 298b93c3a2fdb..c9e954a081ca2 100644 --- a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts @@ -10,15 +10,15 @@ import _ from 'lodash'; import type { KibanaExecutionContext } from '@kbn/core/public'; import type { ControlGroupInput } from '@kbn/controls-plugin/public'; +import { type EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public'; import { compareFilters, - isFilterPinned, - migrateFilter, COMPARE_ALL_OPTIONS, - type Filter, + Filter, + isFilterPinned, + TimeRange, } from '@kbn/es-query'; -import { type EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public'; -import type { TimeRange } from '@kbn/es-query'; +import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; import type { DashboardSavedObject } from '../../saved_dashboards'; import { getTagsFromSavedDashboard, migrateAppState } from '.'; @@ -72,6 +72,7 @@ export const savedObjectToDashboardState = ({ if (rawState.timeRestore) { rawState.timeRange = { from: savedDashboard.timeFrom, to: savedDashboard.timeTo } as TimeRange; } + rawState.controlGroupInput = deserializeControlGroupFromDashboardSavedObject( savedDashboard ) as ControlGroupInput; @@ -89,9 +90,10 @@ export const stateToDashboardContainerInput = ({ executionContext, }: StateToDashboardContainerInputProps): DashboardContainerInput => { const { - data: { query: queryService }, + data: { + query: { filterManager, timefilter: timefilterService }, + }, } = pluginServices.getServices(); - const { filterManager, timefilter: timefilterService } = queryService; const { timefilter } = timefilterService; const { @@ -109,6 +111,7 @@ export const stateToDashboardContainerInput = ({ filters: dashboardFilters, } = dashboardState; + const migratedDashboardFilters = mapAndFlattenFilters(_.cloneDeep(dashboardFilters)); return { refreshConfig: timefilter.getRefreshInterval(), filters: filterManager @@ -116,8 +119,8 @@ export const stateToDashboardContainerInput = ({ .filter( (filter) => isFilterPinned(filter) || - dashboardFilters.some((dashboardFilter) => - filtersAreEqual(migrateFilter(_.cloneDeep(dashboardFilter)), filter) + migratedDashboardFilters.some((dashboardFilter) => + filtersAreEqual(dashboardFilter, filter) ) ), isFullScreenMode: fullScreenMode, diff --git a/src/plugins/data/common/search/search_source/mocks.ts b/src/plugins/data/common/search/search_source/mocks.ts index a17ef12484b65..a980004bd1ceb 100644 --- a/src/plugins/data/common/search/search_source/mocks.ts +++ b/src/plugins/data/common/search/search_source/mocks.ts @@ -49,20 +49,26 @@ export const searchSourceCommonMock: jest.Mocked = { extract: jest.fn(), }; -export const createSearchSourceMock = (fields?: SearchSourceFields, response?: any) => +export const createSearchSourceMock = ( + fields?: SearchSourceFields, + response?: any, + search?: jest.Mock +) => new SearchSource(fields, { aggs: { createAggConfigs: jest.fn(), } as unknown as SearchSourceDependencies['aggs'], getConfig: uiSettingsServiceMock.createStartContract().get, - search: jest.fn().mockReturnValue( - of( - response ?? { - rawResponse: { hits: { hits: [], total: 0 } }, - isPartial: false, - isRunning: false, - } - ) - ), + search: + search || + jest.fn().mockReturnValue( + of( + response ?? { + rawResponse: { hits: { hits: [], total: 0 } }, + isPartial: false, + isRunning: false, + } + ) + ), onResponse: jest.fn().mockImplementation((req, res) => res), }); diff --git a/src/plugins/data_views/common/data_views/data_view.ts b/src/plugins/data_views/common/data_views/data_view.ts index 42ab6d10834d4..f7f6451ca0453 100644 --- a/src/plugins/data_views/common/data_views/data_view.ts +++ b/src/plugins/data_views/common/data_views/data_view.ts @@ -15,7 +15,7 @@ import type { } from '@kbn/field-formats-plugin/common'; import { castEsToKbnFieldTypeName, ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import { CharacterNotAllowedInField } from '@kbn/kibana-utils-plugin/common'; -import _, { cloneDeep, each, reject } from 'lodash'; +import { cloneDeep, each, reject } from 'lodash'; import type { DataViewAttributes, FieldAttrs, FieldAttrSet } from '..'; import type { DataViewField, IIndexPatternFieldList } from '../fields'; import { fieldList } from '../fields'; @@ -407,9 +407,6 @@ export class DataView implements DataViewBase { * Returns index pattern as saved object body for saving */ getAsSavedObjectBody(): DataViewAttributes { - const fieldFormatMap = _.isEmpty(this.fieldFormatMap) - ? undefined - : JSON.stringify(this.fieldFormatMap); const fieldAttrs = this.getFieldAttrs(); const runtimeFieldMap = this.runtimeFieldMap; @@ -419,7 +416,7 @@ export class DataView implements DataViewBase { timeFieldName: this.timeFieldName, sourceFilters: this.sourceFilters ? JSON.stringify(this.sourceFilters) : undefined, fields: JSON.stringify(this.fields?.filter((field) => field.scripted) ?? []), - fieldFormatMap, + fieldFormatMap: this.fieldFormatMap ? JSON.stringify(this.fieldFormatMap) : undefined, type: this.type!, typeMeta: JSON.stringify(this.typeMeta ?? {}), allowNoIndex: this.allowNoIndex ? this.allowNoIndex : undefined, diff --git a/src/plugins/data_views/common/data_views/data_views.test.ts b/src/plugins/data_views/common/data_views/data_views.test.ts index af95942393e11..a096cbe07cd53 100644 --- a/src/plugins/data_views/common/data_views/data_views.test.ts +++ b/src/plugins/data_views/common/data_views/data_views.test.ts @@ -350,6 +350,26 @@ describe('IndexPatterns', () => { expect(async () => await indexPatterns.get(id)).toBeDefined(); }); + test('can set and remove field format', async () => { + const id = 'id'; + setDocsourcePayload(id, savedObject); + const dataView = await indexPatterns.get(id); + dataView.setFieldFormat('field', { id: 'formatId' }); + await indexPatterns.updateSavedObject(dataView); + let lastCall = (savedObjectsClient.update as jest.Mock).mock.calls.pop() ?? []; + let [, , attrs] = lastCall; + expect(attrs).toHaveProperty('fieldFormatMap'); + expect(attrs.fieldFormatMap).toMatchInlineSnapshot(`"{\\"field\\":{\\"id\\":\\"formatId\\"}}"`); + dataView.deleteFieldFormat('field'); + await indexPatterns.updateSavedObject(dataView); + lastCall = (savedObjectsClient.update as jest.Mock).mock.calls.pop() ?? []; + [, , attrs] = lastCall; + + // https://github.com/elastic/kibana/issues/134873: must keep an empty object and not delete it + expect(attrs).toHaveProperty('fieldFormatMap'); + expect(attrs.fieldFormatMap).toMatchInlineSnapshot(`"{}"`); + }); + describe('getDefaultDataView', () => { beforeEach(() => { indexPatterns.clearCache(); diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index cf0bf824c6b1a..b2a42c351a102 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -18,7 +18,8 @@ "dataViewFieldEditor", "dataViewEditor", "expressions", - "unifiedFieldList" + "unifiedFieldList", + "unifiedSearch" ], "optionalPlugins": [ "home", diff --git a/src/plugins/discover/public/__mocks__/data_views.ts b/src/plugins/discover/public/__mocks__/data_views.ts index cb85f317ccd5b..7832a4c0f4e39 100644 --- a/src/plugins/discover/public/__mocks__/data_views.ts +++ b/src/plugins/discover/public/__mocks__/data_views.ts @@ -26,6 +26,7 @@ export const dataViewsMock = { getIdsWithTitle: jest.fn(() => { return Promise.resolve([dataViewMock, dataViewComplexMock, dataViewWithTimefieldMock]); }), + createFilter: jest.fn(), create: jest.fn(), clearInstanceCache: jest.fn(), } as unknown as jest.Mocked; diff --git a/src/plugins/discover/public/__mocks__/services.ts b/src/plugins/discover/public/__mocks__/services.ts index 953422acc9291..252aead2f76ed 100644 --- a/src/plugins/discover/public/__mocks__/services.ts +++ b/src/plugins/discover/public/__mocks__/services.ts @@ -123,4 +123,5 @@ export const discoverServiceMock = { expressions: expressionsPlugin, savedObjectsTagging: {}, dataViews: dataViewsMock, + timefilter: { createFilter: jest.fn() }, } as unknown as DiscoverServices; diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index 9cac8a4f1f50a..1fe847d17beb7 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -17,8 +17,14 @@ import { DiscoverMainRoute } from './main'; import { NotFoundRoute } from './not_found'; import { DiscoverServices } from '../build_services'; import { ViewAlertRoute } from './view_alert'; +import { HistoryLocationState } from '../locator'; -export const discoverRouter = (services: DiscoverServices, history: History, isDev: boolean) => ( +export const discoverRouter = ( + services: DiscoverServices, + history: History, + isDev: boolean, + historyLocationState?: HistoryLocationState +) => ( @@ -39,10 +45,10 @@ export const discoverRouter = (services: DiscoverServices, history: History, isD - + - + diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index 5ae2ed76923b5..2542a639be71f 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -9,8 +9,14 @@ import { i18n } from '@kbn/i18n'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { discoverRouter } from './discover_router'; import { DiscoverServices } from '../build_services'; +import { HistoryLocationState } from '../locator'; -export const renderApp = (element: HTMLElement, services: DiscoverServices, isDev: boolean) => { +export const renderApp = ( + element: HTMLElement, + services: DiscoverServices, + isDev: boolean, + historyLocationState?: HistoryLocationState +) => { const { history: getHistory, capabilities, chrome, data, core } = services; const history = getHistory(); @@ -26,7 +32,7 @@ export const renderApp = (element: HTMLElement, services: DiscoverServices, isDe }); } const unmount = toMountPoint( - wrapWithTheme(discoverRouter(services, history, isDev), core.theme.theme$) + wrapWithTheme(discoverRouter(services, history, isDev, historyLocationState), core.theme.theme$) )(element); return () => { diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx index b55f0fe714853..155d650583899 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx @@ -114,6 +114,7 @@ async function mountComponent(isTimeBased: boolean = false) { viewMode: VIEW_MODE.DOCUMENT_LEVEL, setDiscoverViewMode: jest.fn(), isTimeBased, + onResetChartHeight: jest.fn(), }; let instance: ReactWrapper = {} as ReactWrapper; diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx index 4bc60e904bb84..4912022e08fdd 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx @@ -44,6 +44,7 @@ export function DiscoverChart({ interval, isTimeBased, appendHistogram, + onResetChartHeight, }: { className?: string; resetSavedSearch: () => void; @@ -56,6 +57,7 @@ export function DiscoverChart({ hideChart?: boolean; interval?: string; appendHistogram?: ReactElement; + onResetChartHeight?: () => void; }) { const { data, storage } = useDiscoverServices(); const [showChartOptionsPopover, setShowChartOptionsPopover] = useState(false); @@ -113,13 +115,14 @@ export function DiscoverChart({ }, [data] ); - const panels = useChartPanels( + const panels = useChartPanels({ toggleHideChart, - (newInterval) => stateContainer.setAppState({ interval: newInterval }), - () => setShowChartOptionsPopover(false), + onChangeInterval: (newInterval) => stateContainer.setAppState({ interval: newInterval }), + closePopover: () => setShowChartOptionsPopover(false), + onResetChartHeight, hideChart, - interval - ); + interval, + }); return ( { test('useChartsPanel when hideChart is true', async () => { const { result } = renderHook(() => { - return useChartPanels(jest.fn(), jest.fn(), jest.fn(), true, 'auto'); + return useChartPanels({ + toggleHideChart: jest.fn(), + onChangeInterval: jest.fn(), + closePopover: jest.fn(), + onResetChartHeight: jest.fn(), + hideChart: true, + interval: 'auto', + }); }); const panels: EuiContextMenuPanelDescriptor[] = result.current; const panel0: EuiContextMenuPanelDescriptor = result.current[0]; expect(panels.length).toBe(1); + expect(panel0!.items).toHaveLength(1); expect(panel0!.items![0].icon).toBe('eye'); }); test('useChartsPanel when hideChart is false', async () => { const { result } = renderHook(() => { - return useChartPanels(jest.fn(), jest.fn(), jest.fn(), false, 'auto'); + return useChartPanels({ + toggleHideChart: jest.fn(), + onChangeInterval: jest.fn(), + closePopover: jest.fn(), + onResetChartHeight: jest.fn(), + hideChart: false, + interval: 'auto', + }); }); const panels: EuiContextMenuPanelDescriptor[] = result.current; const panel0: EuiContextMenuPanelDescriptor = result.current[0]; expect(panels.length).toBe(2); + expect(panel0!.items).toHaveLength(3); expect(panel0!.items![0].icon).toBe('eyeClosed'); + expect(panel0!.items![1].icon).toBe('refresh'); + }); + test('should not show reset chart height when onResetChartHeight is undefined', async () => { + const { result } = renderHook(() => { + return useChartPanels({ + toggleHideChart: jest.fn(), + onChangeInterval: jest.fn(), + closePopover: jest.fn(), + hideChart: false, + interval: 'auto', + }); + }); + const panel0: EuiContextMenuPanelDescriptor = result.current[0]; + expect(panel0!.items).toHaveLength(2); + expect(panel0!.items![0].icon).toBe('eyeClosed'); + }); + test('onResetChartHeight is called when the reset chart height button is clicked', async () => { + const onResetChartHeight = jest.fn(); + const { result } = renderHook(() => { + return useChartPanels({ + toggleHideChart: jest.fn(), + onChangeInterval: jest.fn(), + closePopover: jest.fn(), + onResetChartHeight, + hideChart: false, + interval: 'auto', + }); + }); + const panel0: EuiContextMenuPanelDescriptor = result.current[0]; + const resetChartHeightButton = panel0!.items![1]; + (resetChartHeightButton.onClick as Function)(); + expect(onResetChartHeight).toBeCalled(); }); }); diff --git a/src/plugins/discover/public/application/main/components/chart/use_chart_panels.ts b/src/plugins/discover/public/application/main/components/chart/use_chart_panels.ts index bbd670d217e58..f01c72aaee997 100644 --- a/src/plugins/discover/public/application/main/components/chart/use_chart_panels.ts +++ b/src/plugins/discover/public/application/main/components/chart/use_chart_panels.ts @@ -12,13 +12,21 @@ import type { } from '@elastic/eui'; import { search } from '@kbn/data-plugin/public'; -export function useChartPanels( - toggleHideChart: () => void, - onChangeInterval: (value: string) => void, - closePopover: () => void, - hideChart?: boolean, - interval?: string -) { +export function useChartPanels({ + toggleHideChart, + onChangeInterval, + closePopover, + onResetChartHeight, + hideChart, + interval, +}: { + toggleHideChart: () => void; + onChangeInterval: (value: string) => void; + closePopover: () => void; + onResetChartHeight?: () => void; + hideChart?: boolean; + interval?: string; +}) { const selectedOptionIdx = search.aggs.intervalOptions.findIndex((opt) => opt.val === interval); const intervalDisplay = selectedOptionIdx > -1 @@ -43,6 +51,20 @@ export function useChartPanels( }, ]; if (!hideChart) { + if (onResetChartHeight) { + mainPanelItems.push({ + name: i18n.translate('discover.resetChartHeight', { + defaultMessage: 'Reset to default height', + }), + icon: 'refresh', + onClick: () => { + onResetChartHeight(); + closePopover(); + }, + 'data-test-subj': 'discoverChartResetHeight', + }); + } + mainPanelItems.push({ name: i18n.translate('discover.timeIntervalWithValue', { defaultMessage: 'Time interval: {timeInterval}', diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index fbd58853d797c..fe6ac392c35a8 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -255,7 +255,6 @@ export function DiscoverLayout({ viewMode={viewMode} onDataViewCreated={onDataViewCreated} availableFields$={savedSearchData$.availableFields$} - persistDataView={persistDataView} /> diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx index 6b499aa62c430..ae433ee8ba38e 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx @@ -26,7 +26,11 @@ import { FetchStatus } from '../../../types'; import { Chart } from '../chart/point_series'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { buildDataTableRecord } from '../../../../utils/build_data_record'; -import { DiscoverMainContent, DiscoverMainContentProps } from './discover_main_content'; +import { + DiscoverMainContent, + DiscoverMainContentProps, + HISTOGRAM_HEIGHT_KEY, +} from './discover_main_content'; import { VIEW_MODE } from '@kbn/saved-search-plugin/public'; import { DiscoverPanels, DISCOVER_PANELS_MODE } from './discover_panels'; import { euiThemeVars } from '@kbn/ui-theme'; @@ -36,21 +40,29 @@ import { setTimeout } from 'timers/promises'; import { DiscoverChart } from '../chart'; import { ReactWrapper } from 'enzyme'; import { DocumentViewModeToggle } from '../../../../components/view_mode_toggle'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { LocalStorageMock } from '../../../../__mocks__/local_storage_mock'; const mountComponent = async ({ isPlainRecord = false, hideChart = false, isTimeBased = true, + storage, }: { isPlainRecord?: boolean; hideChart?: boolean; isTimeBased?: boolean; + storage?: Storage; } = {}) => { - const services = discoverServiceMock; + let services = discoverServiceMock; services.data.query.timefilter.timefilter.getAbsoluteTime = () => { return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; }; + if (storage) { + services = { ...services, storage }; + } + const main$ = new BehaviorSubject({ fetchStatus: FetchStatus.COMPLETE, recordRawType: isPlainRecord ? RecordRawType.PLAIN : RecordRawType.DOCUMENT, @@ -188,67 +200,145 @@ describe('Discover main content component', () => { window.innerWidth = windowWidth; }); - it('should set the panels mode to DISCOVER_PANELS_MODE.RESIZABLE when viewing on medium screens and above', async () => { - const component = await mountComponent(); - setWindowWidth(component, euiThemeVars.euiBreakpoints.m); - expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.RESIZABLE); - }); + describe('DISCOVER_PANELS_MODE', () => { + it('should set the panels mode to DISCOVER_PANELS_MODE.RESIZABLE when viewing on medium screens and above', async () => { + const component = await mountComponent(); + setWindowWidth(component, euiThemeVars.euiBreakpoints.m); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.RESIZABLE); + }); - it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED when viewing on small screens and below', async () => { - const component = await mountComponent(); - setWindowWidth(component, euiThemeVars.euiBreakpoints.s); - expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); - }); + it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED when viewing on small screens and below', async () => { + const component = await mountComponent(); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); + }); - it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED if hideChart is true', async () => { - const component = await mountComponent({ hideChart: true }); - expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); - }); + it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED if hideChart is true', async () => { + const component = await mountComponent({ hideChart: true }); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); + }); - it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED if isTimeBased is false', async () => { - const component = await mountComponent({ isTimeBased: false }); - expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); - }); + it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED if isTimeBased is false', async () => { + const component = await mountComponent({ isTimeBased: false }); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); + }); - it('should set the panels mode to DISCOVER_PANELS_MODE.SINGLE if isPlainRecord is true', async () => { - const component = await mountComponent({ isPlainRecord: true }); - expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.SINGLE); - }); + it('should set the panels mode to DISCOVER_PANELS_MODE.SINGLE if isPlainRecord is true', async () => { + const component = await mountComponent({ isPlainRecord: true }); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.SINGLE); + }); - it('should set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and hideChart is false', async () => { - const component = await mountComponent(); - setWindowWidth(component, euiThemeVars.euiBreakpoints.s); - const expectedHeight = component.find(DiscoverPanels).prop('initialTopPanelHeight'); - expect(component.find(DiscoverChart).childAt(0).getDOMNode()).toHaveStyle({ - height: `${expectedHeight}px`, + it('should set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and hideChart is false', async () => { + const component = await mountComponent(); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + const expectedHeight = component.find(DiscoverPanels).prop('topPanelHeight'); + expect(component.find(DiscoverChart).childAt(0).getDOMNode()).toHaveStyle({ + height: `${expectedHeight}px`, + }); }); - }); - it('should not set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and hideChart is true', async () => { - const component = await mountComponent({ hideChart: true }); - setWindowWidth(component, euiThemeVars.euiBreakpoints.s); - const expectedHeight = component.find(DiscoverPanels).prop('initialTopPanelHeight'); - expect(component.find(DiscoverChart).childAt(0).getDOMNode()).not.toHaveStyle({ - height: `${expectedHeight}px`, + it('should not set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and hideChart is true', async () => { + const component = await mountComponent({ hideChart: true }); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + const expectedHeight = component.find(DiscoverPanels).prop('topPanelHeight'); + expect(component.find(DiscoverChart).childAt(0).getDOMNode()).not.toHaveStyle({ + height: `${expectedHeight}px`, + }); }); - }); - it('should not set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and isTimeBased is false', async () => { - const component = await mountComponent({ isTimeBased: false }); - setWindowWidth(component, euiThemeVars.euiBreakpoints.s); - const expectedHeight = component.find(DiscoverPanels).prop('initialTopPanelHeight'); - expect(component.find(DiscoverChart).childAt(0).getDOMNode()).not.toHaveStyle({ - height: `${expectedHeight}px`, + it('should not set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and isTimeBased is false', async () => { + const component = await mountComponent({ isTimeBased: false }); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + const expectedHeight = component.find(DiscoverPanels).prop('topPanelHeight'); + expect(component.find(DiscoverChart).childAt(0).getDOMNode()).not.toHaveStyle({ + height: `${expectedHeight}px`, + }); + }); + + it('should pass undefined for onResetChartHeight to DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED', async () => { + const component = await mountComponent(); + expect(component.find(DiscoverChart).prop('onResetChartHeight')).toBeDefined(); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + expect(component.find(DiscoverChart).prop('onResetChartHeight')).toBeUndefined(); }); }); - it('should show DocumentViewModeToggle when isPlainRecord is false', async () => { - const component = await mountComponent(); - expect(component.find(DocumentViewModeToggle).exists()).toBe(true); + describe('DocumentViewModeToggle', () => { + it('should show DocumentViewModeToggle when isPlainRecord is false', async () => { + const component = await mountComponent(); + expect(component.find(DocumentViewModeToggle).exists()).toBe(true); + }); + + it('should not show DocumentViewModeToggle when isPlainRecord is true', async () => { + const component = await mountComponent({ isPlainRecord: true }); + expect(component.find(DocumentViewModeToggle).exists()).toBe(false); + }); }); - it('should not show DocumentViewModeToggle when isPlainRecord is true', async () => { - const component = await mountComponent({ isPlainRecord: true }); - expect(component.find(DocumentViewModeToggle).exists()).toBe(false); + describe('topPanelHeight persistence', () => { + it('should try to get the initial topPanelHeight for DiscoverPanels from storage', async () => { + const storage = new LocalStorageMock({}) as unknown as Storage; + const originalGet = storage.get; + storage.get = jest.fn().mockImplementation(originalGet); + await mountComponent({ storage }); + expect(storage.get).toHaveBeenCalledWith(HISTOGRAM_HEIGHT_KEY); + }); + + it('should pass a default topPanelHeight to DiscoverPanels if no value is found in storage', async () => { + const storage = new LocalStorageMock({}) as unknown as Storage; + const originalGet = storage.get; + storage.get = jest.fn().mockImplementation(originalGet); + const component = await mountComponent({ storage }); + expect(storage.get).toHaveBeenCalledWith(HISTOGRAM_HEIGHT_KEY); + expect(storage.get).toHaveReturnedWith(null); + expect(component.find(DiscoverPanels).prop('topPanelHeight')).toBeGreaterThan(0); + }); + + it('should pass the stored topPanelHeight to DiscoverPanels if a value is found in storage', async () => { + const storage = new LocalStorageMock({}) as unknown as Storage; + const topPanelHeight = 123; + storage.get = jest.fn().mockImplementation(() => topPanelHeight); + const component = await mountComponent({ storage }); + expect(storage.get).toHaveBeenCalledWith(HISTOGRAM_HEIGHT_KEY); + expect(storage.get).toHaveReturnedWith(topPanelHeight); + expect(component.find(DiscoverPanels).prop('topPanelHeight')).toBe(topPanelHeight); + }); + + it('should update the topPanelHeight in storage and pass the new value to DiscoverPanels when the topPanelHeight changes', async () => { + const storage = new LocalStorageMock({}) as unknown as Storage; + const originalSet = storage.set; + storage.set = jest.fn().mockImplementation(originalSet); + const component = await mountComponent({ storage }); + const newTopPanelHeight = 123; + expect(component.find(DiscoverPanels).prop('topPanelHeight')).not.toBe(newTopPanelHeight); + act(() => { + component.find(DiscoverPanels).prop('onTopPanelHeightChange')(newTopPanelHeight); + }); + component.update(); + expect(storage.set).toHaveBeenCalledWith(HISTOGRAM_HEIGHT_KEY, newTopPanelHeight); + expect(component.find(DiscoverPanels).prop('topPanelHeight')).toBe(newTopPanelHeight); + }); + + it('should reset the topPanelHeight to the default when onResetChartHeight is called on DiscoverChart', async () => { + const storage = new LocalStorageMock({}) as unknown as Storage; + const originalSet = storage.set; + storage.set = jest.fn().mockImplementation(originalSet); + const component = await mountComponent({ storage }); + const defaultTopPanelHeight = component.find(DiscoverPanels).prop('topPanelHeight'); + const newTopPanelHeight = 123; + expect(component.find(DiscoverPanels).prop('topPanelHeight')).not.toBe(newTopPanelHeight); + act(() => { + component.find(DiscoverPanels).prop('onTopPanelHeightChange')(newTopPanelHeight); + }); + component.update(); + expect(storage.set).toHaveBeenCalledWith(HISTOGRAM_HEIGHT_KEY, newTopPanelHeight); + expect(component.find(DiscoverPanels).prop('topPanelHeight')).toBe(newTopPanelHeight); + act(() => { + component.find(DiscoverChart).prop('onResetChartHeight')!(); + }); + component.update(); + expect(storage.set).toHaveBeenCalledWith(HISTOGRAM_HEIGHT_KEY, defaultTopPanelHeight); + expect(component.find(DiscoverPanels).prop('topPanelHeight')).toBe(defaultTopPanelHeight); + }); }); }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx index 747ccc9512a5f..68d91c9123a6d 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx @@ -15,7 +15,7 @@ import { useIsWithinBreakpoints, } from '@elastic/eui'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; -import React, { RefObject, useCallback, useMemo } from 'react'; +import React, { RefObject, useCallback, useMemo, useState } from 'react'; import { DataView } from '@kbn/data-views-plugin/common'; import { METRIC_TYPE } from '@kbn/analytics'; import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; @@ -35,6 +35,8 @@ import { DiscoverPanels, DISCOVER_PANELS_MODE } from './discover_panels'; const DiscoverChartMemoized = React.memo(DiscoverChart); const FieldStatisticsTableMemoized = React.memo(FieldStatisticsTable); +export const HISTOGRAM_HEIGHT_KEY = 'discover:histogramHeight'; + export interface DiscoverMainContentProps { isPlainRecord: boolean; dataView: DataView; @@ -74,7 +76,7 @@ export const DiscoverMainContent = ({ columns, resizeRef, }: DiscoverMainContentProps) => { - const { trackUiMetric } = useDiscoverServices(); + const { trackUiMetric, storage } = useDiscoverServices(); const setDiscoverViewMode = useCallback( (mode: VIEW_MODE) => { @@ -104,14 +106,36 @@ export const DiscoverMainContent = ({ const hideChart = state.hideChart || !isTimeBased; const showFixedPanels = useIsWithinBreakpoints(['xs', 's']) || isPlainRecord || hideChart; const { euiTheme } = useEuiTheme(); - const topPanelHeight = euiTheme.base * 12; + const defaultTopPanelHeight = euiTheme.base * 12; const minTopPanelHeight = euiTheme.base * 8; const minMainPanelHeight = euiTheme.base * 10; + const [topPanelHeight, setTopPanelHeight] = useState( + Number(storage.get(HISTOGRAM_HEIGHT_KEY)) || defaultTopPanelHeight + ); + + const storeTopPanelHeight = useCallback( + (newTopPanelHeight: number) => { + storage.set(HISTOGRAM_HEIGHT_KEY, newTopPanelHeight); + setTopPanelHeight(newTopPanelHeight); + }, + [storage] + ); + + const resetTopPanelHeight = useCallback( + () => storeTopPanelHeight(defaultTopPanelHeight), + [storeTopPanelHeight, defaultTopPanelHeight] + ); + + const onTopPanelHeightChange = useCallback( + (newTopPanelHeight: number) => storeTopPanelHeight(newTopPanelHeight), + [storeTopPanelHeight] + ); + const chartClassName = showFixedPanels && !hideChart ? css` - height: ${topPanelHeight}px; + height: ${defaultTopPanelHeight}px; ` : 'eui-fullHeight'; @@ -136,6 +160,9 @@ export const DiscoverMainContent = ({ interval={state.interval} isTimeBased={isTimeBased} appendHistogram={showFixedPanels ? : } + onResetChartHeight={ + panelsMode === DISCOVER_PANELS_MODE.RESIZABLE ? resetTopPanelHeight : undefined + } /> @@ -189,11 +216,12 @@ export const DiscoverMainContent = ({ className="dscPageContent__inner" mode={panelsMode} resizeRef={resizeRef} - initialTopPanelHeight={topPanelHeight} + topPanelHeight={topPanelHeight} minTopPanelHeight={minTopPanelHeight} minMainPanelHeight={minMainPanelHeight} topPanel={} mainPanel={} + onTopPanelHeightChange={onTopPanelHeightChange} /> ); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels.test.tsx index 60ba0038f1194..c136675494fb3 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_panels.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels.test.tsx @@ -34,11 +34,12 @@ describe('Discover panels component', () => { ); }; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels.tsx index 57ea08ee92c8b..b79d9fb96aaeb 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_panels.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels.tsx @@ -20,11 +20,12 @@ export interface DiscoverPanelsProps { className?: string; mode: DISCOVER_PANELS_MODE; resizeRef: RefObject; - initialTopPanelHeight: number; + topPanelHeight: number; minTopPanelHeight: number; minMainPanelHeight: number; topPanel: ReactElement; mainPanel: ReactElement; + onTopPanelHeightChange: (height: number) => void; } const fixedModes = [DISCOVER_PANELS_MODE.SINGLE, DISCOVER_PANELS_MODE.FIXED]; @@ -33,11 +34,12 @@ export const DiscoverPanels = ({ className, mode, resizeRef, - initialTopPanelHeight, + topPanelHeight, minTopPanelHeight, minMainPanelHeight, topPanel, mainPanel, + onTopPanelHeightChange, }: DiscoverPanelsProps) => { const panelsProps = { className, topPanel, mainPanel }; @@ -46,9 +48,10 @@ export const DiscoverPanels = ({ ) : ( ); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.test.tsx index 16c50a94b4656..c919504091c21 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.test.tsx @@ -33,6 +33,7 @@ describe('Discover panels resizable', () => { topPanel = <>, mainPanel = <>, attachTo, + onTopPanelHeightChange = jest.fn(), }: { className?: string; resizeRef?: RefObject; @@ -42,16 +43,18 @@ describe('Discover panels resizable', () => { topPanel?: ReactElement; mainPanel?: ReactElement; attachTo?: HTMLElement; + onTopPanelHeightChange?: (height: number) => void; }) => { return mount( , attachTo ? { attachTo } : undefined ); @@ -95,7 +98,10 @@ describe('Discover panels resizable', () => { it('should set the correct heights of both panels when the panels are resized', () => { const initialTopPanelHeight = 200; - const component = mountComponent({ initialTopPanelHeight }); + const onTopPanelHeightChange = jest.fn((topPanelHeight) => { + component.setProps({ topPanelHeight }).update(); + }); + const component = mountComponent({ initialTopPanelHeight, onTopPanelHeightChange }); expectCorrectPanelSizes(component, containerHeight, initialTopPanelHeight); const newTopPanelSize = 30; const onPanelSizeChange = component @@ -106,7 +112,9 @@ describe('Discover panels resizable', () => { onPanelSizeChange({ [topPanelId]: newTopPanelSize }); }); forceRender(component); - expectCorrectPanelSizes(component, containerHeight, containerHeight * (newTopPanelSize / 100)); + const newTopPanelHeight = (newTopPanelSize / 100) * containerHeight; + expect(onTopPanelHeightChange).toHaveBeenCalledWith(newTopPanelHeight); + expectCorrectPanelSizes(component, containerHeight, newTopPanelHeight); }); it('should maintain the height of the top panel and resize the main panel when the container height changes', () => { diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.tsx index 88a92b4380b76..b65da1eb0bf68 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.tsx @@ -19,23 +19,24 @@ const pixelsToPercent = (containerHeight: number, pixels: number) => export const DiscoverPanelsResizable = ({ className, resizeRef, - initialTopPanelHeight, + topPanelHeight, minTopPanelHeight, minMainPanelHeight, topPanel, mainPanel, + onTopPanelHeightChange, }: { className?: string; resizeRef: RefObject; - initialTopPanelHeight: number; + topPanelHeight: number; minTopPanelHeight: number; minMainPanelHeight: number; topPanel: ReactElement; mainPanel: ReactElement; + onTopPanelHeightChange: (height: number) => void; }) => { const topPanelId = useGeneratedHtmlId({ prefix: 'topPanel' }); const { height: containerHeight } = useResizeObserver(resizeRef.current); - const [topPanelHeight, setTopPanelHeight] = useState(initialTopPanelHeight); const [panelSizes, setPanelSizes] = useState({ topPanelSize: 0, mainPanelSize: 0 }); // EuiResizableContainer doesn't work properly when used with react-reverse-portal and @@ -69,9 +70,13 @@ export const DiscoverPanelsResizable = ({ // the effect below to update the panel sizes. const onPanelSizeChange = useCallback( ({ [topPanelId]: topPanelSize }: { [key: string]: number }) => { - setTopPanelHeight(percentToPixels(containerHeight, topPanelSize)); + const newTopPanelHeight = percentToPixels(containerHeight, topPanelSize); + + if (newTopPanelHeight !== topPanelHeight) { + onTopPanelHeightChange(newTopPanelHeight); + } }, - [containerHeight, topPanelId] + [containerHeight, onTopPanelHeightChange, topPanelHeight, topPanelId] ); // This effect will update the panel sizes based on the top panel height whenever diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx index 5d1414ad488a3..ffb2ec7585ed5 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx @@ -88,7 +88,6 @@ async function getComponent({ onRemoveField: jest.fn(), showFieldStats, selected, - persistDataView: jest.fn(), state: { query: { query: '', language: 'lucene' }, filters: [], diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx index 8fb116f88bdc5..10138dec8b4cb 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx @@ -266,7 +266,6 @@ export interface DiscoverFieldProps { * Optionally show or hide field stats in the popover */ showFieldStats?: boolean; - persistDataView: (dataView: DataView) => Promise; /** * Discover App State @@ -293,7 +292,6 @@ function DiscoverFieldComponent({ onEditField, onDeleteField, showFieldStats, - persistDataView, state, contextualFields, }: DiscoverFieldProps) { @@ -520,7 +518,6 @@ function DiscoverFieldComponent({ multiFields={rawMultiFields} trackUiMetric={trackUiMetric} contextualFields={contextualFields} - persistDataView={persistDataView} /> ); diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize.tsx index dccd664834b53..f60838d375b68 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize.tsx @@ -9,12 +9,7 @@ import React, { useEffect, useState } from 'react'; import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; -import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public'; -import { - getTriggerConstant, - triggerVisualizeActions, - VisualizeInformation, -} from './lib/visualize_trigger_utils'; +import { triggerVisualizeActions, VisualizeInformation } from './lib/visualize_trigger_utils'; import { getVisualizeInformation } from './lib/visualize_trigger_utils'; import { DiscoverFieldVisualizeInner } from './discover_field_visualize_inner'; @@ -24,11 +19,10 @@ interface Props { multiFields?: DataViewField[]; contextualFields: string[]; trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; - persistDataView: (dataView: DataView) => Promise; } export const DiscoverFieldVisualize: React.FC = React.memo( - ({ field, dataView, contextualFields, trackUiMetric, multiFields, persistDataView }) => { + ({ field, dataView, contextualFields, trackUiMetric, multiFields }) => { const [visualizeInfo, setVisualizeInfo] = useState(); useEffect(() => { @@ -47,20 +41,11 @@ export const DiscoverFieldVisualize: React.FC = React.memo( // regular link click. let the uiActions code handle the navigation and show popup if needed event.preventDefault(); - const trigger = getTriggerConstant(field.type); const triggerVisualization = (updatedDataView: DataView) => { trackUiMetric?.(METRIC_TYPE.CLICK, 'visualize_link_click'); triggerVisualizeActions(visualizeInfo.field, contextualFields, updatedDataView); }; - - if (trigger === VISUALIZE_GEO_FIELD_TRIGGER) { - const updatedDataView = await persistDataView(dataView); - if (updatedDataView) { - triggerVisualization(updatedDataView); - } - } else { - triggerVisualization(dataView); - } + triggerVisualization(dataView); }; return ( diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx index 8ee382b76832e..22d33215b1eaf 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx @@ -78,7 +78,6 @@ function getCompProps(): DiscoverSidebarProps { onDataViewCreated: jest.fn(), availableFields$, useNewFieldsApi: true, - persistDataView: jest.fn(), }; } diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx index 7ff64a1070c94..d7e227df94b8c 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx @@ -26,7 +26,7 @@ import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { isEqual } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; -import { DataViewField, getFieldSubtypeMulti, type DataView } from '@kbn/data-views-plugin/public'; +import { DataViewField, getFieldSubtypeMulti } from '@kbn/data-views-plugin/public'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { DiscoverField } from './discover_field'; import { DiscoverFieldSearch } from './discover_field_search'; @@ -93,7 +93,6 @@ export interface DiscoverSidebarProps extends Omit Promise; } export function DiscoverSidebarComponent({ @@ -118,7 +117,6 @@ export function DiscoverSidebarComponent({ createNewDataView, showDataViewPicker, state, - persistDataView, }: DiscoverSidebarProps) { const { uiSettings, dataViewFieldEditor } = useDiscoverServices(); const [fields, setFields] = useState(null); @@ -415,7 +413,6 @@ export function DiscoverSidebarComponent({ onEditField={editField} onDeleteField={deleteField} showFieldStats={showFieldStats} - persistDataView={persistDataView} state={state} contextualFields={columns} /> @@ -478,7 +475,6 @@ export function DiscoverSidebarComponent({ onEditField={editField} onDeleteField={deleteField} showFieldStats={showFieldStats} - persistDataView={persistDataView} state={state} contextualFields={columns} /> @@ -510,7 +506,6 @@ export function DiscoverSidebarComponent({ onEditField={editField} onDeleteField={deleteField} showFieldStats={showFieldStats} - persistDataView={persistDataView} state={state} contextualFields={columns} /> diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.test.tsx index 201033050cc88..3d09302544755 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.test.tsx @@ -175,7 +175,6 @@ function getCompProps(): DiscoverSidebarResponsiveProps { viewMode: VIEW_MODE.DOCUMENT_LEVEL, onDataViewCreated: jest.fn(), useNewFieldsApi: true, - persistDataView: jest.fn(), }; } diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx index 07f4759c42b42..de3b30810859f 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx @@ -106,7 +106,6 @@ export interface DiscoverSidebarResponsiveProps { * list of available fields fetched from ES */ availableFields$: AvailableFields$; - persistDataView: (dataView: DataView) => Promise; } /** diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index d5f693d7f5fb0..4d3dccaa68001 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -29,6 +29,7 @@ import { DiscoverError } from '../../components/common/error_alert'; import { useDiscoverServices } from '../../hooks/use_discover_services'; import { getUrlTracker } from '../../kibana_services'; import { restoreStateFromSavedSearch } from '../../services/saved_searches/restore_from_saved_search'; +import { HistoryLocationState } from '../../locator'; const DiscoverMainAppMemoized = memo(DiscoverMainApp); @@ -38,6 +39,7 @@ interface DiscoverLandingParams { interface Props { isDev: boolean; + historyLocationState?: HistoryLocationState; } export function DiscoverMainRoute(props: Props) { @@ -93,7 +95,12 @@ export function DiscoverMainRoute(props: Props) { const { appStateContainer } = getState({ history, savedSearch: nextSavedSearch, services }); const { index } = appStateContainer.getState(); - const ip = await loadDataView(data.dataViews, config, index); + const ip = await loadDataView( + data.dataViews, + config, + index, + props.historyLocationState?.dataViewSpec + ); const ipList = ip.list; const dataViewData = resolveDataView(ip, nextSavedSearch.searchSource, toastNotifications); @@ -105,7 +112,15 @@ export function DiscoverMainRoute(props: Props) { setError(e); } }, - [config, data.dataViews, history, isDev, toastNotifications, services] + [ + config, + data.dataViews, + history, + isDev, + props.historyLocationState?.dataViewSpec, + toastNotifications, + services, + ] ); const loadSavedSearch = useCallback(async () => { diff --git a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts index a31d700f37a9f..7baede8101851 100644 --- a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts +++ b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts @@ -7,7 +7,12 @@ */ import { i18n } from '@kbn/i18n'; -import type { DataView, DataViewListItem, DataViewsContract } from '@kbn/data-views-plugin/public'; +import type { + DataView, + DataViewListItem, + DataViewsContract, + DataViewSpec, +} from '@kbn/data-views-plugin/public'; import type { ISearchSource } from '@kbn/data-plugin/public'; import type { IUiSettingsClient, ToastsStart } from '@kbn/core/public'; interface DataViewData { @@ -75,12 +80,33 @@ export function getDataViewId( export async function loadDataView( dataViews: DataViewsContract, config: IUiSettingsClient, - id?: string + id?: string, + dataViewSpec?: DataViewSpec ): Promise { const dataViewList = await dataViews.getIdsWithTitle(); + let fetchId: string | undefined = id; + /** + * Handle redirect with data view spec provided via history location state + */ + if (dataViewSpec) { + const isPersisted = dataViewList.find(({ id: currentId }) => currentId === dataViewSpec.id); + if (!isPersisted) { + const createdAdHocDataView = await dataViews.create(dataViewSpec); + return { + list: dataViewList || [], + loaded: createdAdHocDataView, + stateVal: createdAdHocDataView.id, + stateValFound: true, + }; + } + // reassign fetchId in case of persisted data view spec provided + fetchId = dataViewSpec.id!; + } + + // try to fetch adhoc data view first try { - const fetchedDataView = id ? await dataViews.get(id) : undefined; + const fetchedDataView = fetchId ? await dataViews.get(fetchId) : undefined; if (fetchedDataView && !fetchedDataView.isPersisted()) { return { list: dataViewList || [], @@ -95,12 +121,13 @@ export async function loadDataView( // eslint-disable-next-line no-empty } catch (e) {} - const actualId = getDataViewId(id, dataViewList, config.get('defaultIndex')); + // fetch persisted data view + const actualId = getDataViewId(fetchId, dataViewList, config.get('defaultIndex')); return { list: dataViewList || [], loaded: await dataViews.get(actualId), - stateVal: id, - stateValFound: !!id && actualId === id, + stateVal: fetchId, + stateValFound: !!fetchId && actualId === fetchId, }; } diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 5e4140ecb6d48..86675ae54441a 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -45,6 +45,7 @@ import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { DiscoverAppLocator } from './locator'; import { getHistory } from './kibana_services'; import { DiscoverStartPlugins } from './plugin'; @@ -88,6 +89,7 @@ export interface DiscoverServices { charts: ChartsPluginStart; savedObjectsManagement: SavedObjectsManagementPluginStart; savedObjectsTagging?: SavedObjectsTaggingApi; + unifiedSearch: UnifiedSearchPublicPluginStart; } export const buildServices = memoize(function ( @@ -136,5 +138,6 @@ export const buildServices = memoize(function ( charts: plugins.charts, savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(), savedObjectsManagement: plugins.savedObjectsManagement, + unifiedSearch: plugins.unifiedSearch, }; }); diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts new file mode 100644 index 0000000000000..28a4bb185ea76 --- /dev/null +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ReactElement } from 'react'; +import { FilterManager } from '@kbn/data-plugin/public'; +import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock'; +import { getSavedSearchUrl, SearchInput } from '..'; +import { DiscoverServices } from '../build_services'; +import { dataViewMock } from '../__mocks__/data_view'; +import { discoverServiceMock } from '../__mocks__/services'; +import { SavedSearchEmbeddable, SearchEmbeddableConfig } from './saved_search_embeddable'; +import { render } from 'react-dom'; +import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; +import { throwError } from 'rxjs'; +import { ReactWrapper } from 'enzyme'; +import { SavedSearchEmbeddableComponent } from './saved_search_embeddable_component'; + +let discoverComponent: ReactWrapper; + +jest.mock('react-dom', () => { + const { mount } = jest.requireActual('enzyme'); + return { + ...jest.requireActual('react-dom'), + render: jest.fn((component: ReactElement) => { + discoverComponent = mount(component); + }), + }; +}); + +const waitOneTick = () => new Promise((resolve) => setTimeout(resolve, 0)); + +describe('saved search embeddable', () => { + let mountpoint: HTMLDivElement; + let filterManagerMock: jest.Mocked; + let servicesMock: jest.Mocked; + let executeTriggerActions: jest.Mock; + + const createEmbeddable = (searchMock?: jest.Mock) => { + const savedSearchMock = { + id: 'mock-id', + sort: [['message', 'asc']] as Array<[string, string]>, + searchSource: createSearchSourceMock({ index: dataViewMock }, undefined, searchMock), + }; + + const url = getSavedSearchUrl(savedSearchMock.id); + const editUrl = `/app/discover${url}`; + const indexPatterns = [dataViewMock]; + const savedSearchEmbeddableConfig: SearchEmbeddableConfig = { + savedSearch: savedSearchMock, + editUrl, + editPath: url, + editable: true, + indexPatterns, + filterManager: filterManagerMock, + services: servicesMock, + }; + const searchInput: SearchInput = { + id: 'mock-embeddable-id', + timeRange: { from: 'now-15m', to: 'now' }, + columns: ['message', 'extension'], + rowHeight: 30, + rowsPerPage: 50, + }; + + executeTriggerActions = jest.fn(); + + const embeddable = new SavedSearchEmbeddable( + savedSearchEmbeddableConfig, + searchInput, + executeTriggerActions + ); + + // this helps to trigger reload + // eslint-disable-next-line dot-notation + embeddable['inputSubject'].next = jest.fn( + (input) => (input.lastReloadRequestTime = Date.now()) + ); + + return { embeddable }; + }; + + beforeEach(() => { + mountpoint = document.createElement('div'); + filterManagerMock = createFilterManagerMock(); + servicesMock = discoverServiceMock as unknown as jest.Mocked; + }); + + afterEach(() => { + mountpoint.remove(); + }); + + it('should render saved search embeddable two times initially', async () => { + const { embeddable } = createEmbeddable(); + embeddable.updateOutput = jest.fn(); + + embeddable.render(mountpoint); + expect(render).toHaveBeenCalledTimes(1); + + // wait for data fetching + await waitOneTick(); + expect(render).toHaveBeenCalledTimes(2); + }); + + it('should update input correctly', async () => { + const { embeddable } = createEmbeddable(); + embeddable.updateOutput = jest.fn(); + + embeddable.render(mountpoint); + await waitOneTick(); + + const searchProps = discoverComponent.find(SavedSearchEmbeddableComponent).prop('searchProps'); + + searchProps.onAddColumn!('bytes'); + await waitOneTick(); + expect(searchProps.columns).toEqual(['message', 'extension', 'bytes']); + + searchProps.onRemoveColumn!('bytes'); + await waitOneTick(); + expect(searchProps.columns).toEqual(['message', 'extension']); + + searchProps.onSetColumns!(['message', 'bytes', 'extension'], false); + await waitOneTick(); + expect(searchProps.columns).toEqual(['message', 'bytes', 'extension']); + + searchProps.onMoveColumn!('bytes', 2); + await waitOneTick(); + expect(searchProps.columns).toEqual(['message', 'extension', 'bytes']); + + expect(searchProps.rowHeightState).toEqual(30); + searchProps.onUpdateRowHeight!(40); + await waitOneTick(); + expect(searchProps.rowHeightState).toEqual(40); + + expect(searchProps.rowsPerPageState).toEqual(50); + searchProps.onUpdateRowsPerPage!(100); + await waitOneTick(); + expect(searchProps.rowsPerPageState).toEqual(100); + + searchProps.onFilter!({ name: 'customer_id', type: 'string', scripted: false }, [17], '+'); + await waitOneTick(); + expect(executeTriggerActions).toHaveBeenCalled(); + }); + + it('should emit error output in case of fetch error', async () => { + const search = jest.fn().mockReturnValue(throwError(new Error('Fetch error'))); + const { embeddable } = createEmbeddable(search); + embeddable.updateOutput = jest.fn(); + + embeddable.render(mountpoint); + // wait for data fetching + await waitOneTick(); + + expect((embeddable.updateOutput as jest.Mock).mock.calls[1][0].error.message).toBe( + 'Fetch error' + ); + }); +}); diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index 00cbd0a2ffcb0..ffdead82d1dae 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -15,7 +15,7 @@ import { FilterStateStore, } from '@kbn/es-query'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM, { unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; import { isEqual } from 'lodash'; import { I18nProvider } from '@kbn/i18n-react'; @@ -76,7 +76,7 @@ export type SearchProps = Partial & onUpdateRowsPerPage?: (rowsPerPage?: number) => void; }; -interface SearchEmbeddableConfig { +export interface SearchEmbeddableConfig { savedSearch: SavedSearch; editUrl: string; editPath: string; @@ -153,9 +153,9 @@ export class SavedSearchEmbeddable this.searchProps && (titleChanged || this.isFetchRequired(this.searchProps) || - this.isInputChangedAndRerenderRequired(this.searchProps)) + this.isRerenderRequired(this.searchProps)) ) { - this.pushContainerStateParamsToProps(this.searchProps); + this.reload(); } }); } @@ -387,7 +387,7 @@ export class SavedSearchEmbeddable searchSource.setParent(this.filtersSearchSource); - this.pushContainerStateParamsToProps(props); + this.load(props); props.isLoading = true; @@ -420,11 +420,14 @@ export class SavedSearchEmbeddable ); } - private isInputChangedAndRerenderRequired(searchProps?: SearchProps) { + private isRerenderRequired(searchProps?: SearchProps) { if (!searchProps) { return false; } - return this.input.rowsPerPage !== searchProps.rowsPerPageState; + return ( + this.input.rowsPerPage !== searchProps.rowsPerPageState || + (this.input.columns && !isEqual(this.input.columns, searchProps.columns)) + ); } private async pushContainerStateParamsToProps( @@ -466,10 +469,6 @@ export class SavedSearchEmbeddable } else if (this.searchProps && this.node) { this.searchProps = searchProps; } - - if (this.node) { - this.renderReactComponent(this.node, this.searchProps!); - } } /** @@ -480,9 +479,7 @@ export class SavedSearchEmbeddable if (!this.searchProps) { throw new Error('Search props not defined'); } - if (this.node) { - ReactDOM.unmountComponentAtNode(this.node); - } + this.node = domNode; this.renderReactComponent(this.node, this.searchProps!); @@ -545,9 +542,17 @@ export class SavedSearchEmbeddable }); } + private async load(searchProps: SearchProps, forceFetch = false) { + await this.pushContainerStateParamsToProps(searchProps, { forceFetch }); + + if (this.node) { + this.render(this.node); + } + } + public reload() { if (this.searchProps) { - this.pushContainerStateParamsToProps(this.searchProps, { forceFetch: true }); + this.load(this.searchProps, true); } } @@ -584,6 +589,9 @@ export class SavedSearchEmbeddable if (this.searchProps) { delete this.searchProps; } + if (this.node) { + unmountComponentAtNode(this.node); + } this.subscription?.unsubscribe(); if (this.abortController) this.abortController.abort(); diff --git a/src/plugins/discover/public/embeddable/search_embeddable_factory.test.ts b/src/plugins/discover/public/embeddable/search_embeddable_factory.test.ts new file mode 100644 index 0000000000000..fa621879df591 --- /dev/null +++ b/src/plugins/discover/public/embeddable/search_embeddable_factory.test.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { discoverServiceMock } from '../__mocks__/services'; +import { SearchEmbeddableFactory, type StartServices } from './search_embeddable_factory'; +import { getSavedSearch } from '@kbn/saved-search-plugin/public'; +import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; +import { dataViewMock } from '../__mocks__/data_view'; +import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public'; + +jest.mock('@kbn/saved-search-plugin/public', () => { + return { + ...jest.requireActual('@kbn/saved-search-plugin/public'), + getSavedSearch: jest.fn(), + }; +}); + +jest.mock('@kbn/embeddable-plugin/public', () => { + return { + ...jest.requireActual('@kbn/embeddable-plugin/public'), + ErrorEmbeddable: jest.fn(), + }; +}); + +const input = { + id: 'mock-embeddable-id', + timeRange: { from: 'now-15m', to: 'now' }, + columns: ['message', 'extension'], + rowHeight: 30, + rowsPerPage: 50, +}; + +const getSavedSearchMock = getSavedSearch as unknown as jest.Mock; +const ErrorEmbeddableMock = ErrorEmbeddable as unknown as jest.Mock; + +describe('SearchEmbeddableFactory', () => { + it('should create factory correctly', async () => { + const savedSearchMock = { + id: 'mock-id', + sort: [['message', 'asc']] as Array<[string, string]>, + searchSource: createSearchSourceMock({ index: dataViewMock }, undefined), + }; + getSavedSearchMock.mockResolvedValue(savedSearchMock); + + const factory = new SearchEmbeddableFactory( + () => Promise.resolve({ executeTriggerActions: jest.fn() } as unknown as StartServices), + () => Promise.resolve(discoverServiceMock) + ); + const embeddable = await factory.createFromSavedObject('saved-object-id', input); + + expect(getSavedSearchMock.mock.calls[0][0]).toEqual('saved-object-id'); + expect(embeddable).toBeDefined(); + }); + + it('should throw an error when saved search could not be found', async () => { + getSavedSearchMock.mockRejectedValue('Could not find saved search'); + + const factory = new SearchEmbeddableFactory( + () => Promise.resolve({ executeTriggerActions: jest.fn() } as unknown as StartServices), + () => Promise.resolve(discoverServiceMock) + ); + + await factory.createFromSavedObject('saved-object-id', input); + + expect(ErrorEmbeddableMock.mock.calls[0][0]).toEqual('Could not find saved search'); + }); +}); diff --git a/src/plugins/discover/public/embeddable/search_embeddable_factory.ts b/src/plugins/discover/public/embeddable/search_embeddable_factory.ts index 185b0daf8055b..bdf0371aceaa8 100644 --- a/src/plugins/discover/public/embeddable/search_embeddable_factory.ts +++ b/src/plugins/discover/public/embeddable/search_embeddable_factory.ts @@ -26,7 +26,7 @@ import { SEARCH_EMBEDDABLE_TYPE } from './constants'; import { SavedSearchEmbeddable } from './saved_search_embeddable'; import { DiscoverServices } from '../build_services'; -interface StartServices { +export interface StartServices { executeTriggerActions: UiActionsStart['executeTriggerActions']; isEditable: () => boolean; } diff --git a/src/plugins/discover/public/locator.test.ts b/src/plugins/discover/public/locator.test.ts index 274e339c91343..221c7a4958fa6 100644 --- a/src/plugins/discover/public/locator.test.ts +++ b/src/plugins/discover/public/locator.test.ts @@ -20,9 +20,7 @@ interface SetupParams { } const setup = async ({ useHash = false }: SetupParams = {}) => { - const locator = new DiscoverAppLocatorDefinition({ - useHash, - }); + const locator = new DiscoverAppLocatorDefinition({ useHash }); return { locator, @@ -230,6 +228,18 @@ describe('Discover url generator', () => { expect(path).toEqual(legacyParamsPath); }); + test('should create data view when dataViewSpec is used', async () => { + const dataViewSpecMock = { + id: 'mock-id', + title: 'mock-title', + timeFieldName: 'mock-time-field-name', + }; + const { locator } = await setup(); + const { state } = await locator.getLocation({ dataViewSpec: dataViewSpecMock }); + + expect(state.dataViewSpec).toEqual(dataViewSpecMock); + }); + describe('useHash property', () => { describe('when default useHash is set to false', () => { test('when using default, sets data view ID in the generated URL', async () => { diff --git a/src/plugins/discover/public/locator.ts b/src/plugins/discover/public/locator.ts index 0fa2195cc246f..eb8d1039f27c5 100644 --- a/src/plugins/discover/public/locator.ts +++ b/src/plugins/discover/public/locator.ts @@ -11,6 +11,7 @@ import type { Filter, TimeRange, Query, AggregateQuery } from '@kbn/es-query'; import type { GlobalQueryStateFromUrl, RefreshInterval } from '@kbn/data-plugin/public'; import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import { DataViewSpec } from '@kbn/data-views-plugin/public'; import type { VIEW_MODE } from './components/view_mode_toggle'; export const DISCOVER_APP_LOCATOR = 'DISCOVER_APP_LOCATOR'; @@ -30,6 +31,7 @@ export interface DiscoverAppLocatorParams extends SerializableRecord { * @deprecated */ indexPatternId?: string; + dataViewSpec?: DataViewSpec; /** * Optionally set the time range in the time picker. @@ -97,6 +99,10 @@ export interface DiscoverAppLocatorDependencies { useHash: boolean; } +export interface HistoryLocationState { + dataViewSpec?: DataViewSpec; +} + export class DiscoverAppLocatorDefinition implements LocatorDefinition { public readonly id = DISCOVER_APP_LOCATOR; @@ -108,6 +114,7 @@ export class DiscoverAppLocatorDefinition implements LocatorDefinition('_g', queryState, { useHash }, path); path = setStateToKbnUrl('_a', appState, { useHash }, path); @@ -161,7 +173,7 @@ export class DiscoverAppLocatorDefinition implements LocatorDefinition { @@ -304,7 +310,7 @@ export class DiscoverPlugin // FIXME: Temporarily hide overflow-y in Discover app when Field Stats table is shown // due to EUI bug https://github.com/elastic/eui/pull/5152 params.element.classList.add('dscAppWrapper'); - const unmount = renderApp(params.element, services, isDev); + const unmount = renderApp(params.element, services, isDev, historyLocationState); return () => { unlistenParentHistory(); unmount(); diff --git a/src/plugins/event_annotation/common/constants.ts b/src/plugins/event_annotation/common/constants.ts index 3338450b64ce5..3f3f9877b9786 100644 --- a/src/plugins/event_annotation/common/constants.ts +++ b/src/plugins/event_annotation/common/constants.ts @@ -19,6 +19,7 @@ export const AvailableAnnotationIcons = { MAP_MARKER: 'mapMarker', PIN_FILLED: 'pinFilled', STAR_EMPTY: 'starEmpty', + STAR_FILLED: 'starFilled', TAG: 'tag', TRIANGLE: 'triangle', } as const; diff --git a/src/plugins/guided_onboarding/README.md b/src/plugins/guided_onboarding/README.md index c0f16b034d043..1daa04d223e2b 100755 --- a/src/plugins/guided_onboarding/README.md +++ b/src/plugins/guided_onboarding/README.md @@ -1,9 +1,77 @@ -# guidedOnboarding +# Guided Onboarding -A Kibana plugin +This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages. + +The guided onboarding plugin includes a client-side code for the UI and the server side code for the internal API. The server-side code is not intended for external use. + +The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins. --- ## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. +1. To enable the UI, add `guidedOnboarding.ui: true` to the file `KIBANA_FOLDER/config/kibana.dev.yml`. + +2. Start Kibana with examples `yarn start --run-examples` to be able to see the guidedOnboardingExample plugin. + +3. Navigate to `/app/guidedOnboardingExample` to start a guide and check the button in the header. + +## API service +*Also see `KIBANA_FOLDER/examples/guided_onboarding_example` for code examples.* + +The guided onboarding plugin exposes an API service from its start contract that is intended to be used by other plugins. The API service allows consumers to access the current state of the guided onboarding process and manipulate it. + +To use the API service in your plugin, declare the guided onboarding plugin as a dependency in the file `kibana.json` of your plugin. Add the API service to your plugin's start dependencies to rely on the provided TypeScript interface: +``` +export interface AppPluginStartDependencies { + guidedOnboarding: GuidedOnboardingPluginStart; +} +``` +The API service is now available to your plugin in the setup lifecycle function of your plugin +``` +// startDependencies is of type AppPluginStartDependencies +const [coreStart, startDependencies] = await core.getStartServices(); +``` +or in the start lifecycle function of your plugin. +``` +public start(core: CoreStart, startDependencies: AppPluginStartDependencies) { + ... +} +``` + +### isGuideStepActive$(guideID: string, stepID: string): Observable\ +*Also see `KIBANA_FOLDER/examples/guided_onboarding_example/public/components/step_one.tsx`.* + +The API service exposes an Observable that contains a boolean value for the state of a specific guide step. For example, if your plugin needs to check if the "Add data" step of the Security guide is currently active, you could use the following code snippet. + +``` +const { guidedOnboardingApi } = guidedOnboarding; +const isDataStepActive = useObservable(guidedOnboardingApi!.isGuideStepActive$('security', 'add_data')); +useEffect(() => { + // do some logic depending on the step state +}, [isDataStepActive]); +``` + +Alternatively, you can subscribe to the Observable directly. +``` +useEffect(() => { + const subscription = guidedOnboardingApi?.isGuideStepActive$('security', 'add_data').subscribe((isDataStepACtive) => { + // do some logic depending on the step state + }); + return () => subscription?.unsubscribe(); +}, [guidedOnboardingApi]); +``` + +### completeGuideStep(guideID: string, stepID: string): Promise\<{ state: GuidedOnboardingState } | undefined\> +The API service exposes an async function to mark a guide step as completed. +If the specified guide step is not currently active, the function is a noop. The return value is `undefined` in that case, +otherwise an updated `GuidedOnboardingState` is returned *(This is WIP and will likely change in the 8.6 dev cycle)*. + +``` +await guidedOnboardingApi?.completeGuideStep('security', 'add_data'); +``` + +## Guides config +To use the API service, you need to know a guide ID (one of `search`, `observability`, `security`) and a step ID (for example, `add_data`, `search_experience`, `rules` etc). Refer to guides config files in the folder `./public/constants` for more information. + + diff --git a/src/plugins/guided_onboarding/jest.config.js b/src/plugins/guided_onboarding/jest.config.js new file mode 100644 index 0000000000000..dbff307ed09e4 --- /dev/null +++ b/src/plugins/guided_onboarding/jest.config.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/guided_onboarding'], + testRunner: 'jasmine2', + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/guided_onboarding', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/src/plugins/guided_onboarding/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts b/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts new file mode 100644 index 0000000000000..1ba565c9caee0 --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel.styles.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 { EuiThemeComputed } from '@elastic/eui'; +import { css } from '@emotion/react'; + +/** + * + * Style overrides for the setup guide dropdown panel. + * There is currently no existing EUI component that fully supports what we need. + * In order to leverage a11y features, we are using the EuiFlyout and applying customizations + * See https://github.com/elastic/eui/issues/6241 for more details + */ +export const getGuidePanelStyles = (euiTheme: EuiThemeComputed) => ({ + flyoutOverrides: { + flyoutContainer: css` + top: 55px !important; + bottom: 25px !important; + right: 128px; + border-radius: 6px; + width: 480px; + height: auto; + animation: euiModal 350ms cubic-bezier(0.34, 1.61, 0.7, 1); + box-shadow: none; + "@media only screen and (max-width: 574px)": { + right: 25px; + width: 100%; + }, + `, + flyoutBody: css` + .euiFlyoutBody__overflowContent { + width: 480px; + padding-top: 10px; + } + `, + flyoutFooter: css` + border-radius: 0 0 6px 6px; + background: ${euiTheme.colors.ghost}; + padding: 24px 30px; + `, + }, +}); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx new file mode 100644 index 0000000000000..5eaf24163d2ae --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { act } from 'react-dom/test-utils'; +import React from 'react'; + +import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; +import { httpServiceMock } from '@kbn/core/public/mocks'; +import { HttpSetup } from '@kbn/core/public'; + +import { apiService } from '../services/api'; +import { guidesConfig } from '../constants/guides_config'; +import { GuidePanel } from './guide_panel'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; + +const applicationMock = applicationServiceMock.createStartContract(); + +const getGuidePanel = () => () => { + return ; +}; + +describe('GuidePanel', () => { + let httpClient: jest.Mocked; + let testBed: TestBed; + + beforeEach(async () => { + httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); + // Set default state on initial request (no active guides) + httpClient.get.mockResolvedValue({ + state: { activeGuide: 'unset', activeStep: 'unset' }, + }); + apiService.setup(httpClient); + + await act(async () => { + const GuidePanelComponent = getGuidePanel(); + testBed = registerTestBed(GuidePanelComponent)(); + }); + + testBed.component.update(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('it should be disabled in there is no active guide', async () => { + const { exists } = testBed; + expect(exists('disabledGuideButton')).toBe(true); + expect(exists('guideButton')).toBe(false); + expect(exists('guidePanel')).toBe(false); + }); + + test('it should be enabled if there is an active guide', async () => { + const { exists, component, find } = testBed; + + await act(async () => { + // Enable the "search" guide + await apiService.updateGuideState({ + activeGuide: 'search', + activeStep: guidesConfig.search.steps[0].id, + }); + }); + + component.update(); + + expect(exists('disabledGuideButton')).toBe(false); + expect(exists('guideButton')).toBe(true); + expect(exists('guidePanel')).toBe(true); + expect(find('guidePanelStep').length).toEqual(guidesConfig.search.steps.length); + }); +}); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx new file mode 100644 index 0000000000000..f32f55e42b340 --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -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 and the Server Side Public License, v 1; you may not 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, useEffect, useRef } from 'react'; +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiButton, + EuiText, + EuiProgress, + EuiHorizontalRule, + EuiSpacer, + htmlIdGenerator, + EuiButtonEmpty, + EuiTitle, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + useEuiTheme, +} from '@elastic/eui'; + +import { ApplicationStart } from '@kbn/core-application-browser'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { guidesConfig } from '../constants/guides_config'; +import type { GuideConfig, StepStatus, GuidedOnboardingState, StepConfig } from '../types'; +import type { ApiService } from '../services/api'; + +import { GuideStep } from './guide_panel_step'; +import { getGuidePanelStyles } from './guide_panel.styles'; + +interface GuidePanelProps { + api: ApiService; + application: ApplicationStart; +} + +const getConfig = (state?: GuidedOnboardingState): GuideConfig | undefined => { + if (state?.activeGuide && state.activeGuide !== 'unset') { + return guidesConfig[state.activeGuide]; + } + + return undefined; +}; + +const getCurrentStep = ( + steps?: StepConfig[], + state?: GuidedOnboardingState +): number | undefined => { + if (steps && state?.activeStep) { + const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === state.activeStep); + if (activeStepIndex > -1) { + return activeStepIndex + 1; + } + + return undefined; + } +}; + +const getStepStatus = (steps: StepConfig[], stepIndex: number, activeStep?: string): StepStatus => { + const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === activeStep); + + if (activeStepIndex < stepIndex) { + return 'incomplete'; + } + + if (activeStepIndex === stepIndex) { + return 'in_progress'; + } + + return 'complete'; +}; + +export const GuidePanel = ({ api, application }: GuidePanelProps) => { + const { euiTheme } = useEuiTheme(); + const [isGuideOpen, setIsGuideOpen] = useState(false); + const [guideState, setGuideState] = useState(undefined); + const isFirstRender = useRef(true); + + const styles = getGuidePanelStyles(euiTheme); + + const toggleGuide = () => { + setIsGuideOpen((prevIsGuideOpen) => !prevIsGuideOpen); + }; + + const navigateToStep = (step: StepConfig) => { + setIsGuideOpen(false); + if (step.location) { + application.navigateToApp(step.location.appID, { path: step.location.path }); + } + }; + + const navigateToLandingPage = () => { + setIsGuideOpen(false); + application.navigateToApp('home', { path: '#getting_started' }); + }; + + useEffect(() => { + const subscription = api.fetchGuideState$().subscribe((newState) => { + if ( + guideState?.activeGuide !== newState.activeGuide || + guideState?.activeStep !== newState.activeStep + ) { + if (isFirstRender.current) { + isFirstRender.current = false; + } else { + setIsGuideOpen(true); + } + } + setGuideState(newState); + }); + return () => subscription.unsubscribe(); + }, [api, guideState?.activeGuide, guideState?.activeStep]); + + const guideConfig = getConfig(guideState); + + // TODO handle loading, error state + // https://github.com/elastic/kibana/issues/139799, https://github.com/elastic/kibana/issues/139798 + if (!guideConfig) { + return ( + + {i18n.translate('guidedOnboarding.disabledGuidedSetupButtonLabel', { + defaultMessage: 'Setup guide', + })} + + ); + } + + const currentStep = getCurrentStep(guideConfig.steps, guideState); + + return ( + <> + + {currentStep + ? i18n.translate('guidedOnboarding.guidedSetupStepButtonLabel', { + defaultMessage: 'Setup guide: Step {currentStep}', + values: { + currentStep, + }, + }) + : i18n.translate('guidedOnboarding.guidedSetupButtonLabel', { + defaultMessage: 'Setup guide', + })} + + + {isGuideOpen && ( + + + + {i18n.translate('guidedOnboarding.dropdownPanel.backToGuidesLink', { + defaultMessage: 'Back to guides', + })} + + + +

{guideConfig?.title}

+ + + + + + + +
+ +

{guideConfig?.description}

+
+ + {guideConfig.docs && ( + <> + + + + {guideConfig.docs.text} + + + + )} + + + + {/* + TODO: Progress bar should only show after the first step has been started + We need to make changes to the state itself in order to support this + */} + + + + + + + {guideConfig?.steps.map((step, index, steps) => { + const accordionId = htmlIdGenerator(`accordion${index}`)(); + const stepStatus = getStepStatus(steps, index, guideState?.activeStep); + + return ( + + ); + })} +
+
+ + + + + {/* TODO: Implement exit guide modal - https://github.com/elastic/kibana/issues/139804 */} + {}}> + {i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', { + defaultMessage: 'Exit setup guide', + })} + + + + + + + {i18n.translate('guidedOnboarding.dropdownPanel.footer.feedbackLabel', { + defaultMessage: 'feedback', + })} + + ), + }} + /> + + + + + + + {i18n.translate( + 'guidedOnboarding.dropdownPanel.footer.helpTextDescription', + { + defaultMessage: 'here to help', + } + )} + + ), + }} + /> + + + + + + )} + + ); +}; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts new file mode 100644 index 0000000000000..498059564e6ea --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiThemeComputed } from '@elastic/eui'; +import { css } from '@emotion/react'; + +export const getGuidePanelStepStyles = (euiTheme: EuiThemeComputed) => ({ + stepNumber: css` + width: 24px; + height: 24px; + border-radius: 50%; + border: 2px solid ${euiTheme.colors.success}; + font-weight: ${euiTheme.font.weight.medium}; + text-align: center; + line-height: 1.4; + `, + stepTitle: css` + font-weight: ${euiTheme.font.weight.bold}; + `, +}); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx new file mode 100644 index 0000000000000..e6a300b6b6742 --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { + EuiButton, + EuiText, + EuiAccordion, + EuiHorizontalRule, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + useEuiTheme, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import type { StepStatus, StepConfig } from '../types'; +import { getGuidePanelStepStyles } from './guide_panel_step.styles'; + +interface GuideStepProps { + accordionId: string; + stepStatus: StepStatus; + stepConfig: StepConfig; + stepNumber: number; + navigateToStep: (step: StepConfig) => void; +} + +export const GuideStep = ({ + accordionId, + stepStatus, + stepNumber, + stepConfig, + navigateToStep, +}: GuideStepProps) => { + const { euiTheme } = useEuiTheme(); + const styles = getGuidePanelStepStyles(euiTheme); + + const buttonContent = ( + + + {stepStatus === 'complete' ? ( + + ) : ( + {stepNumber} + )} + + + + {stepConfig.title} + + + + ); + + return ( +
+ + <> + + + +
    + {stepConfig.descriptionList.map((description, index) => { + return
  • {description}
  • ; + })} +
+
+ + + {stepStatus === 'in_progress' && ( + + + navigateToStep(stepConfig)} fill> + {/* TODO: Support for conditional "Continue" button label if user revists a step - https://github.com/elastic/kibana/issues/139752 */} + {i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { + defaultMessage: 'Start', + })} + + + + )} + +
+ + +
+ ); +}; diff --git a/src/plugins/guided_onboarding/public/components/index.ts b/src/plugins/guided_onboarding/public/components/index.ts index 131e5c328e97e..e13e9f105158a 100644 --- a/src/plugins/guided_onboarding/public/components/index.ts +++ b/src/plugins/guided_onboarding/public/components/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { GuidedOnboardingButton } from './onboarding_button'; +export { GuidePanel } from './guide_panel'; diff --git a/src/plugins/guided_onboarding/public/components/onboarding_button.tsx b/src/plugins/guided_onboarding/public/components/onboarding_button.tsx deleted file mode 100644 index bab27ec007a19..0000000000000 --- a/src/plugins/guided_onboarding/public/components/onboarding_button.tsx +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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, useEffect, useRef } from 'react'; -import { css } from '@emotion/react'; -import { - EuiPopover, - EuiPopoverTitle, - EuiPopoverFooter, - EuiButton, - EuiText, - EuiProgress, - EuiAccordion, - EuiHorizontalRule, - EuiSpacer, - EuiTextColor, - htmlIdGenerator, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - useEuiTheme, - EuiButtonEmpty, - EuiTitle, -} from '@elastic/eui'; - -import { ApplicationStart } from '@kbn/core-application-browser'; -import { HttpStart } from '@kbn/core-http-browser'; -import { i18n } from '@kbn/i18n'; -import { guidesConfig } from '../constants'; -import type { GuideConfig, StepStatus, GuidedOnboardingState, StepConfig } from '../types'; -import type { ApiService } from '../services/api'; - -interface Props { - api: ApiService; - application: ApplicationStart; - http: HttpStart; -} - -const getConfig = (state?: GuidedOnboardingState): GuideConfig | undefined => { - if (state?.activeGuide && state.activeGuide !== 'unset') { - return guidesConfig[state.activeGuide]; - } - - return undefined; -}; - -const getStepLabel = (steps?: StepConfig[], state?: GuidedOnboardingState): string => { - if (steps && state?.activeStep) { - const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === state.activeStep); - if (activeStepIndex > -1) { - return `: Step ${activeStepIndex + 1}`; - } - } - return ''; -}; - -const getStepStatus = (steps: StepConfig[], stepIndex: number, activeStep?: string): StepStatus => { - const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === activeStep); - if (activeStepIndex < stepIndex) { - return 'incomplete'; - } - if (activeStepIndex === stepIndex) { - return 'in_progress'; - } - return 'complete'; -}; - -export const GuidedOnboardingButton = ({ api, application, http }: Props) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - const [guidedOnboardingState, setGuidedOnboardingState] = useState< - GuidedOnboardingState | undefined - >(undefined); - - const firstRender = useRef(true); - - useEffect(() => { - const subscription = api.fetchGuideState$().subscribe((newState) => { - if ( - guidedOnboardingState?.activeGuide !== newState.activeGuide || - guidedOnboardingState?.activeStep !== newState.activeStep - ) { - if (firstRender.current) { - firstRender.current = false; - } else { - setIsPopoverOpen(true); - } - } - setGuidedOnboardingState(newState); - }); - return () => subscription.unsubscribe(); - }, [api, guidedOnboardingState?.activeGuide, guidedOnboardingState?.activeStep]); - - const { euiTheme } = useEuiTheme(); - - const togglePopover = () => { - setIsPopoverOpen((prevIsPopoverOpen) => !prevIsPopoverOpen); - }; - - const popoverContainerCss = css` - width: 400px; - `; - - const statusCircleCss = ({ status }: { status: StepStatus }) => css` - width: 24px; - height: 24px; - border-radius: 32px; - ${(status === 'complete' || status === 'in_progress') && - `background-color: ${euiTheme.colors.success};`} - ${status === 'incomplete' && - ` - border: 2px solid ${euiTheme.colors.lightShade}; - `} - `; - - const guideConfig = getConfig(guidedOnboardingState); - const stepLabel = getStepLabel(guideConfig?.steps, guidedOnboardingState); - - const navigateToStep = (step: StepConfig) => { - setIsPopoverOpen(false); - if (step.location) { - application.navigateToApp(step.location.appID, { path: step.location.path }); - } - }; - - return guideConfig ? ( - - {i18n.translate('guidedOnboarding.guidedSetupButtonLabel', { - defaultMessage: 'Guided setup{stepLabel}', - values: { - stepLabel, - }, - })} - - } - isOpen={isPopoverOpen} - closePopover={() => setIsPopoverOpen(false)} - anchorPosition="downRight" - hasArrow={false} - offset={10} - panelPaddingSize="l" - > - - {}} - iconSide="left" - iconType="arrowLeft" - isDisabled={true} - flush="left" - > - {i18n.translate('guidedOnboarding.dropdownPanel.backToGuidesLink', { - defaultMessage: 'Back to guides', - })} - - -

{guideConfig?.title}

-
-
- -
- -

{guideConfig?.description}

-
- - - - - {guideConfig?.steps.map((step, index, steps) => { - const accordionId = htmlIdGenerator(`accordion${index}`)(); - - const stepStatus = getStepStatus(steps, index, guidedOnboardingState?.activeStep); - const buttonContent = ( - - - - {stepStatus} - {stepStatus === 'complete' && } - - - {step.title} - - ); - - return ( -
- - <> - - {step.description} - - {stepStatus === 'in_progress' && ( - - - navigateToStep(step)} fill> - {/* TODO: Support for conditional "Continue" button label if user revists a step */} - {i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { - defaultMessage: 'Start', - })} - - - - )} - - - - {/* Do not show horizontal rule for last item */} - {guideConfig.steps.length - 1 !== index && } -
- ); - })} - - - -

- {i18n.translate('guidedOnboarding.dropdownPanel.footerDescription', { - defaultMessage: `Got questions? We're here to help.`, - })} -

-
-
-
-
-
- ) : ( - - Guided setup - - ); -}; diff --git a/src/plugins/guided_onboarding/public/constants/index.ts b/src/plugins/guided_onboarding/public/constants/guides_config.ts similarity index 84% rename from src/plugins/guided_onboarding/public/constants/index.ts rename to src/plugins/guided_onboarding/public/constants/guides_config.ts index 700f035c8c8dd..0cbee9d4b12b6 100644 --- a/src/plugins/guided_onboarding/public/constants/index.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config.ts @@ -6,14 +6,10 @@ * Side Public License, v 1. */ +import { GuidesConfig } from '../types'; import { securityConfig } from './security'; import { observabilityConfig } from './observability'; import { searchConfig } from './search'; -import type { GuideConfig, UseCase } from '../types'; - -type GuidesConfig = { - [key in UseCase]: GuideConfig; -}; export const guidesConfig: GuidesConfig = { security: securityConfig, diff --git a/src/plugins/guided_onboarding/public/constants/observability.ts b/src/plugins/guided_onboarding/public/constants/observability.ts index 72d2a70dfa44d..3f96ad1268173 100644 --- a/src/plugins/guided_onboarding/public/constants/observability.ts +++ b/src/plugins/guided_onboarding/public/constants/observability.ts @@ -20,38 +20,29 @@ export const observabilityConfig: GuideConfig = { { id: 'add_data', title: 'Add data', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { - id: 'rules', - title: 'Customize your alerting rules', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + id: 'view_dashboard', + title: 'View Kubernetes metrics', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { - id: 'infrastructure', - title: 'View infrastructure details', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - }, - { - id: 'explore', - title: 'Explore Discover and Dashboards', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - }, - { - id: 'tour', - title: 'Tour Observability', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - }, - { - id: 'do_more', - title: 'Do more with Observability', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + id: 'tour_observability', + title: 'Tour Elastic Observability', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, ], }; diff --git a/src/plugins/guided_onboarding/public/constants/search.ts b/src/plugins/guided_onboarding/public/constants/search.ts index 8f6ff8fb7600b..b4c3c151aca0c 100644 --- a/src/plugins/guided_onboarding/public/constants/search.ts +++ b/src/plugins/guided_onboarding/public/constants/search.ts @@ -10,7 +10,7 @@ import type { GuideConfig } from '../types'; export const searchConfig: GuideConfig = { title: 'Search my data', - description: `We'll help you build world-class search experiences with your data.`, + description: `We'll help you build world-class search experiences with your data, using Elastic's out-of-the-box web crawler, connectors, and our robust APIs. Gain deep insights from the built-in search analytics and use that data to inform changes to relevance.`, docs: { text: 'Enterprise Search 101 Documentation', url: 'example.com', @@ -19,34 +19,37 @@ export const searchConfig: GuideConfig = { { id: 'add_data', title: 'Add data', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], location: { appID: 'guidedOnboardingExample', path: 'stepOne', }, }, { - id: 'search_experience', - title: 'Build a search experience', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + id: 'browse_docs', + title: 'Browse your documents', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], location: { appID: 'guidedOnboardingExample', path: 'stepTwo?showTour=true', }, }, { - id: 'optimize', - title: 'Optimize your search relevance', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - }, - { - id: 'review', - title: 'Review your search analytics', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + id: 'search_experience', + title: 'Build a search experience', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, ], }; diff --git a/src/plugins/guided_onboarding/public/constants/security.ts b/src/plugins/guided_onboarding/public/constants/security.ts index f8447f81dc019..2c19e7acc2bed 100644 --- a/src/plugins/guided_onboarding/public/constants/security.ts +++ b/src/plugins/guided_onboarding/public/constants/security.ts @@ -16,28 +16,38 @@ export const securityConfig: GuideConfig = { { id: 'add_data', title: 'Add and view your data', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { id: 'rules', title: 'Turn on rules', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { id: 'alerts', title: 'View Alerts', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, { id: 'cases', title: 'Cases and investigations', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - }, - { - id: 'do_more', - title: 'Do more with Elastic Security', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + descriptionList: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', + 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + ], }, ], }; diff --git a/src/plugins/guided_onboarding/public/plugin.tsx b/src/plugins/guided_onboarding/public/plugin.tsx index 9b2e95656cb80..902acaa899e3a 100755 --- a/src/plugins/guided_onboarding/public/plugin.tsx +++ b/src/plugins/guided_onboarding/public/plugin.tsx @@ -16,7 +16,6 @@ import { Plugin, CoreTheme, ApplicationStart, - HttpStart, PluginInitializerContext, } from '@kbn/core/public'; @@ -26,7 +25,7 @@ import { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart, } from './types'; -import { GuidedOnboardingButton } from './components'; +import { GuidePanel } from './components'; import { ApiService, apiService } from './services/api'; export class GuidedOnboardingPlugin @@ -56,7 +55,6 @@ export class GuidedOnboardingPlugin theme$: theme.theme$, api: apiService, application, - http, }), }); @@ -73,18 +71,16 @@ export class GuidedOnboardingPlugin theme$, api, application, - http, }: { targetDomElement: HTMLElement; theme$: Rx.Observable; api: ApiService; application: ApplicationStart; - http: HttpStart; }) { ReactDOM.render( - + , targetDomElement diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts new file mode 100644 index 0000000000000..9f5e20cb9f89d --- /dev/null +++ b/src/plugins/guided_onboarding/public/services/api.test.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 and the Server Side Public License, v 1; you may not 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 { httpServiceMock } from '@kbn/core/public/mocks'; +import { firstValueFrom, Subscription } from 'rxjs'; + +import { API_BASE_PATH } from '../../common'; +import { ApiService } from './api'; +import { GuidedOnboardingState } from '..'; +import { guidesConfig } from '../constants/guides_config'; + +const searchGuide = 'search'; +const firstStep = guidesConfig[searchGuide].steps[0].id; +const secondStep = guidesConfig[searchGuide].steps[1].id; +const lastStep = guidesConfig[searchGuide].steps[2].id; + +describe('GuidedOnboarding ApiService', () => { + let httpClient: jest.Mocked; + let apiService: ApiService; + let subscription: Subscription; + + beforeEach(() => { + httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); + httpClient.get.mockResolvedValue({ + state: { activeGuide: searchGuide, activeStep: firstStep }, + }); + apiService = new ApiService(); + apiService.setup(httpClient); + }); + + afterEach(() => { + if (subscription) { + subscription.unsubscribe(); + } + jest.restoreAllMocks(); + }); + + describe('fetchGuideState$', () => { + it('sends a request to the get API', () => { + subscription = apiService.fetchGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(httpClient.get).toHaveBeenCalledWith(`${API_BASE_PATH}/state`); + }); + + it('broadcasts the updated state', async () => { + await apiService.updateGuideState({ + activeGuide: searchGuide, + activeStep: secondStep, + }); + + const state = await firstValueFrom(apiService.fetchGuideState$()); + expect(state).toEqual({ activeGuide: searchGuide, activeStep: secondStep }); + }); + }); + + describe('updateGuideState', () => { + it('sends a request to the put API', async () => { + const state = { + activeGuide: searchGuide, + activeStep: secondStep, + }; + await apiService.updateGuideState(state as GuidedOnboardingState); + expect(httpClient.put).toHaveBeenCalledTimes(1); + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify(state), + }); + }); + }); + + describe('isGuideStepActive$', () => { + it('returns true if the step is active', async (done) => { + subscription = apiService + .isGuideStepActive$(searchGuide, firstStep) + .subscribe((isStepActive) => { + if (isStepActive) { + done(); + } + }); + }); + + it('returns false if the step is not active', async (done) => { + subscription = apiService + .isGuideStepActive$(searchGuide, secondStep) + .subscribe((isStepActive) => { + if (!isStepActive) { + done(); + } + }); + }); + }); + + describe('completeGuideStep', () => { + it(`completes the step when it's active`, async () => { + await apiService.completeGuideStep(searchGuide, firstStep); + expect(httpClient.put).toHaveBeenCalledTimes(1); + // this assertion depends on the guides config, we are checking for the next step + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + activeGuide: searchGuide, + activeStep: secondStep, + }), + }); + }); + + it(`completes the guide when the last step is active`, async () => { + httpClient.get.mockResolvedValue({ + // this state depends on the guides config + state: { activeGuide: searchGuide, activeStep: lastStep }, + }); + apiService.setup(httpClient); + + await apiService.completeGuideStep(searchGuide, lastStep); + expect(httpClient.put).toHaveBeenCalledTimes(1); + // this assertion depends on the guides config, we are checking for the last step + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + activeGuide: searchGuide, + activeStep: 'completed', + }), + }); + }); + + it(`does nothing if the step is not active`, async () => { + await apiService.completeGuideStep(searchGuide, secondStep); + expect(httpClient.put).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 543b4ffc30f41..b99975c3a837a 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -7,10 +7,11 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { BehaviorSubject, map, from, concatMap, of } from 'rxjs'; +import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; import { API_BASE_PATH } from '../../common'; -import { GuidedOnboardingState } from '../types'; +import { GuidedOnboardingState, UseCase } from '../types'; +import { getNextStep, isLastStep } from './helpers'; export class ApiService { private client: HttpSetup | undefined; @@ -21,7 +22,12 @@ export class ApiService { this.onboardingGuideState$ = new BehaviorSubject(undefined); } - public fetchGuideState$() { + /** + * An Observable with the guided onboarding state. + * Initially the state is fetched from the backend. + * Subsequently, the observable is updated automatically, when the state changes. + */ + public fetchGuideState$(): Observable { // TODO add error handling if this.client has not been initialized or request fails return this.onboardingGuideState$.pipe( concatMap((state) => @@ -34,7 +40,14 @@ export class ApiService { ); } - public async updateGuideState(newState: GuidedOnboardingState) { + /** + * Updates the state of the guided onboarding + * @param {GuidedOnboardingState} newState the new state of the guided onboarding + * @return {Promise} a promise with the updated state or undefined if the update fails + */ + public async updateGuideState( + newState: GuidedOnboardingState + ): Promise<{ state: GuidedOnboardingState } | undefined> { if (!this.client) { throw new Error('ApiService has not be initialized.'); } @@ -54,6 +67,51 @@ export class ApiService { console.error(error); } } + + /** + * An observable with the boolean value if the step is active. + * Returns true, if the passed params identify the guide step that is currently active. + * Returns false otherwise. + * @param {string} guideID the id of the guide (one of search, observability, security) + * @param {string} stepID the id of the step in the guide + * @return {Observable} an observable with the boolean value + */ + public isGuideStepActive$(guideID: string, stepID: string): Observable { + return this.fetchGuideState$().pipe( + map((state) => { + return state ? state.activeGuide === guideID && state.activeStep === stepID : false; + }) + ); + } + + /** + * Completes the guide step identified by the passed params. + * A noop if the passed step is not active. + * Completes the current guide, if the step is the last one in the guide. + * @param {string} guideID the id of the guide (one of search, observability, security) + * @param {string} stepID the id of the step in the guide + * @return {Promise} a promise with the updated state or undefined if the operation fails + */ + public async completeGuideStep( + guideID: string, + stepID: string + ): Promise<{ state: GuidedOnboardingState } | undefined> { + const isStepActive = await firstValueFrom(this.isGuideStepActive$(guideID, stepID)); + if (isStepActive) { + if (isLastStep(guideID, stepID)) { + await this.updateGuideState({ activeGuide: guideID as UseCase, activeStep: 'completed' }); + } else { + const nextStepID = getNextStep(guideID, stepID); + if (nextStepID !== undefined) { + await this.updateGuideState({ + activeGuide: guideID as UseCase, + activeStep: nextStepID, + }); + } + } + } + return undefined; + } } export const apiService = new ApiService(); diff --git a/src/plugins/guided_onboarding/public/services/helpers.test.ts b/src/plugins/guided_onboarding/public/services/helpers.test.ts new file mode 100644 index 0000000000000..6e1a3cc3e0049 --- /dev/null +++ b/src/plugins/guided_onboarding/public/services/helpers.test.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 { guidesConfig } from '../constants/guides_config'; +import { getNextStep, isLastStep } from './helpers'; + +const searchGuide = 'search'; +const firstStep = guidesConfig[searchGuide].steps[0].id; +const secondStep = guidesConfig[searchGuide].steps[1].id; +const lastStep = guidesConfig[searchGuide].steps[2].id; + +describe('GuidedOnboarding ApiService helpers', () => { + // this test suite depends on the guides config + describe('isLastStepActive', () => { + it('returns true if the passed params are for the last step', () => { + const result = isLastStep(searchGuide, lastStep); + expect(result).toBe(true); + }); + + it('returns false if the passed params are not for the last step', () => { + const result = isLastStep(searchGuide, firstStep); + expect(result).toBe(false); + }); + }); + + describe('getNextStep', () => { + it('returns id of the next step', () => { + const result = getNextStep(searchGuide, firstStep); + expect(result).toEqual(secondStep); + }); + + it('returns undefined if the params are not part of the config', () => { + const result = getNextStep('some_guide', 'some_step'); + expect(result).toBeUndefined(); + }); + + it(`returns undefined if it's the last step`, () => { + const result = getNextStep(searchGuide, lastStep); + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/src/plugins/guided_onboarding/public/services/helpers.ts b/src/plugins/guided_onboarding/public/services/helpers.ts new file mode 100644 index 0000000000000..3eb0bfca9b751 --- /dev/null +++ b/src/plugins/guided_onboarding/public/services/helpers.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { guidesConfig } from '../constants/guides_config'; +import { GuideConfig, StepConfig, UseCase } from '../types'; + +export const getGuideConfig = (guideID?: string): GuideConfig | undefined => { + if (guideID && Object.keys(guidesConfig).includes(guideID)) { + return guidesConfig[guideID as UseCase]; + } +}; + +const getStepIndex = (guideID: string, stepID: string): number => { + const guide = getGuideConfig(guideID); + if (guide) { + return guide.steps.findIndex((step: StepConfig) => step.id === stepID); + } + return -1; +}; + +export const isLastStep = (guideID: string, stepID: string): boolean => { + const guide = getGuideConfig(guideID); + const activeStepIndex = getStepIndex(guideID, stepID); + const stepsNumber = guide?.steps.length || 0; + if (stepsNumber > 0) { + return activeStepIndex === stepsNumber - 1; + } + return false; +}; + +export const getNextStep = (guideID: string, stepID: string): string | undefined => { + const guide = getGuideConfig(guideID); + const activeStepIndex = getStepIndex(guideID, stepID); + if (activeStepIndex > -1 && guide?.steps[activeStepIndex + 1]) { + return guide?.steps[activeStepIndex + 1].id; + } +}; diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 8beb1772a511e..7925fa8ae69d7 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -26,7 +26,7 @@ export type StepStatus = 'incomplete' | 'complete' | 'in_progress'; export interface StepConfig { id: string; title: string; - description: string; + descriptionList: string[]; location?: { appID: string; path: string; @@ -44,9 +44,13 @@ export interface GuideConfig { steps: StepConfig[]; } +export type GuidesConfig = { + [key in UseCase]: GuideConfig; +}; + export interface GuidedOnboardingState { activeGuide: UseCase | 'unset'; - activeStep: string | 'unset'; + activeStep: string | 'unset' | 'completed'; } export interface ClientConfigType { diff --git a/src/plugins/interactive_setup/public/theme/kibana_theme_provider.tsx b/src/plugins/interactive_setup/public/theme/kibana_theme_provider.tsx index 9ac4fa6bccd10..29d2bd893a87d 100644 --- a/src/plugins/interactive_setup/public/theme/kibana_theme_provider.tsx +++ b/src/plugins/interactive_setup/public/theme/kibana_theme_provider.tsx @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { EuiProviderProps } from '@elastic/eui'; import { EuiProvider } from '@elastic/eui'; import createCache from '@emotion/cache'; import type { FC } from 'react'; @@ -19,6 +20,7 @@ import { getColorMode } from './utils'; interface KibanaThemeProviderProps { theme$: Observable; + modify?: EuiProviderProps<{}>['modify']; } const defaultTheme: CoreTheme = { @@ -38,7 +40,7 @@ emotionCache.compat = true; /** * Copied from the `kibana_react` plugin, remove once https://github.com/elastic/kibana/issues/119204 is implemented. */ -export const KibanaThemeProvider: FC = ({ theme$, children }) => { +export const KibanaThemeProvider: FC = ({ theme$, modify, children }) => { const theme = useObservable(theme$, defaultTheme); const colorMode = useMemo(() => getColorMode(theme), [theme]); return ( @@ -47,6 +49,7 @@ export const KibanaThemeProvider: FC = ({ theme$, chil cache={{ default: emotionCache, global: globalCache }} globalStyles={false} utilityClasses={false} + modify={modify} > {children} diff --git a/src/plugins/kibana_react/public/theme/kibana_theme_provider.tsx b/src/plugins/kibana_react/public/theme/kibana_theme_provider.tsx index 52fd1e49eb648..b5715d3f57b62 100644 --- a/src/plugins/kibana_react/public/theme/kibana_theme_provider.tsx +++ b/src/plugins/kibana_react/public/theme/kibana_theme_provider.tsx @@ -9,13 +9,14 @@ import React, { FC, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; -import { EuiProvider } from '@elastic/eui'; +import { EuiProvider, EuiProviderProps } from '@elastic/eui'; import createCache from '@emotion/cache'; import type { CoreTheme } from '@kbn/core/public'; import { getColorMode } from './utils'; interface KibanaThemeProviderProps { theme$: Observable; + modify?: EuiProviderProps<{}>['modify']; } const defaultTheme: CoreTheme = { @@ -36,7 +37,7 @@ emotionCache.compat = true; That copy and this comment can be removed once https://github.com/elastic/kibana/issues/119204 is implemented.*/ // IMPORTANT: This code has been copied to the `kibana_utils` plugin, to avoid cyclical dependency, any changes here should be applied there too. -export const KibanaThemeProvider: FC = ({ theme$, children }) => { +export const KibanaThemeProvider: FC = ({ theme$, modify, children }) => { const theme = useObservable(theme$, defaultTheme); const colorMode = useMemo(() => getColorMode(theme), [theme]); return ( @@ -45,6 +46,7 @@ export const KibanaThemeProvider: FC = ({ theme$, chil cache={{ default: emotionCache, global: globalCache }} globalStyles={false} utilityClasses={false} + modify={modify} > {children} diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts index ce8e27f318cfd..28a921dd20162 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts @@ -822,6 +822,46 @@ export function getCoreUsageCollector( 'How many times this API has been called by a non-Kibana client in a custom space.', }, }, + 'apiCalls.savedObjectsBulkDelete.total': { + type: 'long', + _meta: { description: 'How many times this API has been called.' }, + }, + 'apiCalls.savedObjectsBulkDelete.namespace.default.total': { + type: 'long', + _meta: { description: 'How many times this API has been called in the Default space.' }, + }, + 'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes': { + type: 'long', + _meta: { + description: + 'How many times this API has been called by the Kibana client in the Default space.', + }, + }, + 'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no': { + type: 'long', + _meta: { + description: + 'How many times this API has been called by a non-Kibana client in the Default space.', + }, + }, + 'apiCalls.savedObjectsBulkDelete.namespace.custom.total': { + type: 'long', + _meta: { description: 'How many times this API has been called in a custom space.' }, + }, + 'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes': { + type: 'long', + _meta: { + description: + 'How many times this API has been called by the Kibana client in a custom space.', + }, + }, + 'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no': { + type: 'long', + _meta: { + description: + 'How many times this API has been called by a non-Kibana client in a custom space.', + }, + }, // Saved Objects Management APIs 'apiCalls.savedObjectsImport.total': { type: 'long', diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_data.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_data.ts index 36aac8ec0511a..2e5084f1b9ae5 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_data.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_data.ts @@ -38,6 +38,13 @@ export interface CoreUsageStats { 'apiCalls.savedObjectsBulkResolve.namespace.custom.total'?: number; 'apiCalls.savedObjectsBulkResolve.namespace.custom.kibanaRequest.yes'?: number; 'apiCalls.savedObjectsBulkResolve.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkDelete.total'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.default.total'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.custom.total'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsBulkUpdate.total'?: number; 'apiCalls.savedObjectsBulkUpdate.namespace.default.total'?: number; 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes'?: number; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index b20d0a7c3f3bd..41df488839358 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -438,6 +438,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:apmEnableServiceMetrics': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'banners:placement': { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, @@ -522,6 +526,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:enableAwsLambdaMetrics': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'observability:apmProgressiveLoading': { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, @@ -554,10 +562,6 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'enterpriseSearch:enableIndexTransformsTab': { - type: 'boolean', - _meta: { description: 'Non-default value of setting.' }, - }, 'enterpriseSearch:enableBehavioralAnalyticsSection': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index f6aaf1258c818..2bd59dc69084f 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -42,6 +42,7 @@ export interface UsageStats { 'observability:maxSuggestions': number; 'observability:enableComparisonByDefault': boolean; 'observability:enableServiceGroups': boolean; + 'observability:apmEnableServiceMetrics': boolean; 'observability:enableInfrastructureHostsView': boolean; 'visualize:enableLabs': boolean; 'visualization:heatmap:maxBuckets': number; @@ -141,6 +142,7 @@ export interface UsageStats { 'metrics:allowCheckingForFailedShards': boolean; 'observability:apmOperationsTab': boolean; 'observability:apmLabsButton': boolean; + 'observability:enableAwsLambdaMetrics': boolean; 'observability:apmProgressiveLoading': string; 'observability:apmServiceGroupMaxNumberOfServices': number; 'observability:apmServiceInventoryOptimizedSorting': boolean; @@ -148,6 +150,5 @@ export interface UsageStats { 'securitySolution:enableGroupedNav': boolean; 'securitySolution:showRelatedIntegrations': boolean; 'visualization:visualize:legacyGaugeChartsLibrary': boolean; - 'enterpriseSearch:enableIndexTransformsTab': boolean; 'enterpriseSearch:enableBehavioralAnalyticsSection': boolean; } diff --git a/src/plugins/kibana_utils/public/theme/kibana_theme_provider.tsx b/src/plugins/kibana_utils/public/theme/kibana_theme_provider.tsx index 68690b57c8283..92434d412c84c 100644 --- a/src/plugins/kibana_utils/public/theme/kibana_theme_provider.tsx +++ b/src/plugins/kibana_utils/public/theme/kibana_theme_provider.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EuiProvider } from '@elastic/eui'; +import { EuiProvider, EuiProviderProps } from '@elastic/eui'; import createCache from '@emotion/cache'; import type { FC } from 'react'; import React, { useMemo } from 'react'; @@ -18,6 +18,7 @@ import { getColorMode } from './utils'; interface KibanaThemeProviderProps { theme$: Observable; + modify?: EuiProviderProps<{}>['modify']; } const defaultTheme: CoreTheme = { @@ -37,7 +38,7 @@ emotionCache.compat = true; /** * Copied from the `kibana_react` plugin, to avoid cyclical dependency */ -export const KibanaThemeProvider: FC = ({ theme$, children }) => { +export const KibanaThemeProvider: FC = ({ theme$, modify, children }) => { const theme = useObservable(theme$, defaultTheme); const colorMode = useMemo(() => getColorMode(theme), [theme]); return ( @@ -46,6 +47,7 @@ export const KibanaThemeProvider: FC = ({ theme$, chil cache={{ default: emotionCache, global: globalCache }} globalStyles={false} utilityClasses={false} + modify={modify} > {children} diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 5d962699345b0..1a97586dffa62 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -7222,6 +7222,48 @@ "description": "How many times this API has been called by a non-Kibana client in a custom space." } }, + "apiCalls.savedObjectsBulkDelete.total": { + "type": "long", + "_meta": { + "description": "How many times this API has been called." + } + }, + "apiCalls.savedObjectsBulkDelete.namespace.default.total": { + "type": "long", + "_meta": { + "description": "How many times this API has been called in the Default space." + } + }, + "apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes": { + "type": "long", + "_meta": { + "description": "How many times this API has been called by the Kibana client in the Default space." + } + }, + "apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no": { + "type": "long", + "_meta": { + "description": "How many times this API has been called by a non-Kibana client in the Default space." + } + }, + "apiCalls.savedObjectsBulkDelete.namespace.custom.total": { + "type": "long", + "_meta": { + "description": "How many times this API has been called in a custom space." + } + }, + "apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes": { + "type": "long", + "_meta": { + "description": "How many times this API has been called by the Kibana client in a custom space." + } + }, + "apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no": { + "type": "long", + "_meta": { + "description": "How many times this API has been called by a non-Kibana client in a custom space." + } + }, "apiCalls.savedObjectsImport.total": { "type": "long", "_meta": { @@ -8660,6 +8702,12 @@ "description": "Non-default value of setting." } }, + "observability:apmEnableServiceMetrics": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "banners:placement": { "type": "keyword", "_meta": { @@ -8786,6 +8834,12 @@ "description": "Non-default value of setting." } }, + "observability:enableAwsLambdaMetrics": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "observability:apmProgressiveLoading": { "type": "keyword", "_meta": { @@ -8834,12 +8888,6 @@ "description": "Non-default value of setting." } }, - "enterpriseSearch:enableIndexTransformsTab": { - "type": "boolean", - "_meta": { - "description": "Non-default value of setting." - } - }, "enterpriseSearch:enableBehavioralAnalyticsSection": { "type": "boolean", "_meta": { diff --git a/src/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts b/src/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts index 1efe08490bf30..4cbd2038dfef4 100644 --- a/src/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts +++ b/src/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts @@ -6,13 +6,14 @@ * Side Public License, v 1. */ -import { LicenseType } from '@kbn/licensing-plugin/public'; -import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; -import { PersistableStateDefinition } from '@kbn/kibana-utils-plugin/common'; -import { +import type { LicenseType } from '@kbn/licensing-plugin/public'; +import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import type { PersistableStateDefinition, UiComponent } from '@kbn/kibana-utils-plugin/common'; +import type { ActionFactoryDefinition, BaseActionConfig, BaseActionFactoryContext, + SerializedAction, SerializedEvent, } from '../dynamic_actions'; @@ -109,10 +110,19 @@ export interface DrilldownDefinition< /** * Should return an internationalized name of the drilldown, which will be - * displayed to the user. + * displayed to the user as the name of drilldown factory when configuring a drilldown. */ getDisplayName: () => string; + /** + * Name of the drilldown instance displayed to the user at the moment of + * drilldown execution. Should be internationalized. + */ + readonly actionMenuItem?: UiComponent<{ + config: Omit, 'factoryId'>; + context: ExecutionContext | ActionExecutionContext; + }>; + /** * isCompatible during execution * Could be used to prevent drilldown from execution diff --git a/src/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts b/src/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts index 9de325e281357..9b80a3316d2f1 100644 --- a/src/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts +++ b/src/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts @@ -110,6 +110,7 @@ export class UiActionsServiceEnhancements createConfig, isConfigValid, getDisplayName, + actionMenuItem, euiIcon, execute, getHref, @@ -143,6 +144,17 @@ export class UiActionsServiceEnhancements type: factoryId, getIconType: () => euiIcon, getDisplayName: () => serializedAction.name, + MenuItem: actionMenuItem + ? () => { + const comp = actionMenuItem(); + return { + render: (el, { context }) => { + comp.render(el, { context, config: serializedAction }); + }, + unmount: comp.unmount, + }; + } + : undefined, execute: async (context) => await execute(serializedAction.config, context), getHref: getHref ? async (context) => getHref(serializedAction.config, context) : undefined, isCompatible: isCompatible diff --git a/src/plugins/unified_search/kibana.json b/src/plugins/unified_search/kibana.json index 07e438ab52174..0dc0627ea6f4b 100755 --- a/src/plugins/unified_search/kibana.json +++ b/src/plugins/unified_search/kibana.json @@ -11,6 +11,7 @@ "ui": true, "requiredPlugins": ["dataViews", "data", "uiActions", "screenshotMode"], "requiredBundles": ["kibanaUtils", "kibanaReact", "data"], + "optionalPlugins": ["usageCollection"], "serviceFolders": ["autocomplete"], "configPath": ["unifiedSearch"] } diff --git a/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx b/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx index 30b4d993e6954..40d3abfb7fae0 100644 --- a/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx +++ b/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx @@ -566,4 +566,46 @@ storiesOf('SearchBar', module) }, isDisabled: true, } as SearchBarProps) + ) + .add('no submit button', () => + wrapSearchBarInContext({ + dataViewPickerComponentProps: { + currentDataViewId: '1234', + trigger: { + 'data-test-subj': 'dataView-switch-link', + label: 'logstash-*', + title: 'logstash-*', + }, + onChangeDataView: action('onChangeDataView'), + }, + showSubmitButton: false, + } as SearchBarProps) + ) + .add('submit button always as icon', () => + wrapSearchBarInContext({ + dataViewPickerComponentProps: { + currentDataViewId: '1234', + trigger: { + 'data-test-subj': 'dataView-switch-link', + label: 'logstash-*', + title: 'logstash-*', + }, + onChangeDataView: action('onChangeDataView'), + }, + submitButtonStyle: 'iconOnly', + } as SearchBarProps) + ) + .add('submit button always as a full button', () => + wrapSearchBarInContext({ + dataViewPickerComponentProps: { + currentDataViewId: '1234', + trigger: { + 'data-test-subj': 'dataView-switch-link', + label: 'logstash-*', + title: 'logstash-*', + }, + onChangeDataView: action('onChangeDataView'), + }, + submitButtonStyle: 'full', + } as SearchBarProps) ); diff --git a/src/plugins/unified_search/public/plugin.ts b/src/plugins/unified_search/public/plugin.ts index e853e6b77e8e1..645652cd60f34 100755 --- a/src/plugins/unified_search/public/plugin.ts +++ b/src/plugins/unified_search/public/plugin.ts @@ -53,6 +53,7 @@ export class UnifiedSearchPublicPlugin ); uiActions.registerAction(createUpdateFilterReferencesAction(query.filterManager)); + this.usageCollection = usageCollection; return { autocomplete: this.autocomplete.setup(core, { diff --git a/src/plugins/unified_search/public/query_string_input/index.tsx b/src/plugins/unified_search/public/query_string_input/index.tsx index 536df031edaa7..a92295b646535 100644 --- a/src/plugins/unified_search/public/query_string_input/index.tsx +++ b/src/plugins/unified_search/public/query_string_input/index.tsx @@ -7,8 +7,7 @@ */ import React from 'react'; -import { AggregateQuery, Query } from '@kbn/es-query'; -import { withKibana } from '@kbn/kibana-react-plugin/public'; +import type { AggregateQuery, Query } from '@kbn/es-query'; import type { QueryBarTopRowProps } from './query_bar_top_row'; import type { QueryStringInputProps } from './query_string_input'; @@ -24,7 +23,7 @@ export const QueryBarTopRow = ( ); -const LazyQueryStringInputUI = withKibana(React.lazy(() => import('./query_string_input'))); +const LazyQueryStringInputUI = React.lazy(() => import('./query_string_input')); export const QueryStringInput = (props: QueryStringInputProps) => ( }> diff --git a/src/plugins/unified_search/public/query_string_input/language_switcher.test.tsx b/src/plugins/unified_search/public/query_string_input/language_switcher.test.tsx index 591fe94360793..2289876fcea73 100644 --- a/src/plugins/unified_search/public/query_string_input/language_switcher.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/language_switcher.test.tsx @@ -8,24 +8,14 @@ import React from 'react'; import { QueryLanguageSwitcher, QueryLanguageSwitcherProps } from './language_switcher'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { coreMock } from '@kbn/core/public/mocks'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EuiButtonIcon, EuiIcon, EuiPopover } from '@elastic/eui'; const startMock = coreMock.createStart(); describe('LanguageSwitcher', () => { - function wrapInContext(testProps: QueryLanguageSwitcherProps) { - const services = { - uiSettings: startMock.uiSettings, - docLinks: startMock.docLinks, - }; - - return ( - - - - ); + function wrapInContext(testProps: Omit) { + return ; } it('should select the lucene context menu if language is lucene', () => { diff --git a/src/plugins/unified_search/public/query_string_input/language_switcher.tsx b/src/plugins/unified_search/public/query_string_input/language_switcher.tsx index edd2028fda48c..0c369b4efc077 100644 --- a/src/plugins/unified_search/public/query_string_input/language_switcher.tsx +++ b/src/plugins/unified_search/public/query_string_input/language_switcher.tsx @@ -18,7 +18,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { DocLinksStart } from '@kbn/core/public'; export interface QueryLanguageSwitcherProps { language: string; @@ -27,6 +27,9 @@ export interface QueryLanguageSwitcherProps { nonKqlMode?: 'lucene' | 'text'; isOnTopBarMenu?: boolean; isDisabled?: boolean; + deps: { + docLinks: DocLinksStart; + }; } export const QueryLanguageSwitcher = React.memo(function QueryLanguageSwitcher({ @@ -36,9 +39,9 @@ export const QueryLanguageSwitcher = React.memo(function QueryLanguageSwitcher({ nonKqlMode = 'lucene', isOnTopBarMenu, isDisabled, + deps: { docLinks }, }: QueryLanguageSwitcherProps) { - const kibana = useKibana(); - const kueryQuerySyntaxDocs = kibana.services.docLinks!.links.query.kueryQuerySyntax; + const kueryQuerySyntaxDocs = docLinks.links.query.kueryQuerySyntax; const [isPopoverOpen, setIsPopoverOpen] = useState(false); const button = ( diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx index 4a921c3a1d177..c36124ca7f448 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu_panels.tsx @@ -492,6 +492,9 @@ export function QueryBarMenuPanels({ onSelectLanguage={onSelectLanguage} nonKqlMode={nonKqlMode} isOnTopBarMenu={true} + deps={{ + docLinks: kibana.services.docLinks, + }} /> ), }, diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx index 052e0ab7b32c8..7be320d5d7aeb 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx @@ -208,6 +208,23 @@ describe('QueryBarTopRowTopRow', () => { expect(component.find(TIMEPICKER_SELECTOR).length).toBe(1); }); + it('Should render update button as icon button', () => { + const component = mount( + wrapQueryBarTopRowInContext({ + isDirty: false, + screenTitle: 'Another Screen', + showDatePicker: true, + showSubmitButton: true, + submitButtonStyle: 'iconOnly', + dateRangeFrom: 'now-7d', + dateRangeTo: 'now', + timeHistory: mockTimeHistory, + }) + ); + + expect(component.find(REFRESH_BUTTON_SELECTOR).prop('iconOnly')).toBe(true); + }); + it('Should render the timefilter duration container for sharing', () => { const component = mount( wrapQueryBarTopRowInContext({ 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 c0848f630daa8..0780b05778ca0 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 @@ -15,6 +15,7 @@ import type { Filter, TimeRange, Query, AggregateQuery } from '@kbn/es-query'; import { getAggregateQueryMode, isOfQueryType, isOfAggregateQueryType } from '@kbn/es-query'; import { EMPTY } from 'rxjs'; import { map } from 'rxjs/operators'; +import { throttle } from 'lodash'; import { EuiFlexGroup, EuiFlexItem, @@ -30,7 +31,7 @@ import { TimeHistoryContract, getQueryLog } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; import { DataView } from '@kbn/data-views-plugin/public'; import type { PersistedLog } from '@kbn/data-plugin/public'; -import { useKibana, withKibana } from '@kbn/kibana-react-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import type { IUnifiedSearchPluginServices } from '../types'; import QueryStringInputUI from './query_string_input'; @@ -51,8 +52,6 @@ const SuperDatePicker = React.memo( EuiSuperDatePicker as any ) as unknown as typeof EuiSuperDatePicker; -const QueryStringInput = withKibana(QueryStringInputUI); - // @internal export interface QueryBarTopRowProps { customSubmitButton?: any; @@ -93,6 +92,13 @@ export interface QueryBarTopRowProps filterBar?: React.ReactNode; showDatePickerAsBadge?: boolean; showSubmitButton?: boolean; + /** + * Style of the submit button + * `iconOnly` - use IconButton + * `full` - use SuperUpdateButton + * (default) `auto` - `iconOnly` on smaller screens, and `full` on larger screens + */ + submitButtonStyle?: 'auto' | 'iconOnly' | 'full'; suggestionsSize?: SuggestionsListSize; isScreenshotMode?: boolean; onTextLangQuerySubmit: (query?: Query | AggregateQuery) => void; @@ -142,18 +148,23 @@ export const QueryBarTopRow = React.memo( const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isXXLarge, setIsXXLarge] = useState(false); const [codeEditorIsExpanded, setCodeEditorIsExpanded] = useState(false); + const submitButtonStyle: QueryBarTopRowProps['submitButtonStyle'] = + props.submitButtonStyle ?? 'auto'; + const submitButtonIconOnly = + submitButtonStyle === 'auto' ? !isXXLarge : submitButtonStyle === 'iconOnly'; useEffect(() => { - function handleResize() { + if (submitButtonStyle !== 'auto') return; + + const handleResize = throttle(() => { setIsXXLarge(window.innerWidth >= 1440); - } + }, 50); - window.removeEventListener('resize', handleResize); window.addEventListener('resize', handleResize); handleResize(); return () => window.removeEventListener('resize', handleResize); - }, []); + }, [submitButtonStyle]); const { showQueryInput = true, @@ -166,7 +177,19 @@ export const QueryBarTopRow = React.memo( const [isQueryInputFocused, setIsQueryInputFocused] = useState(false); const kibana = useKibana(); - const { uiSettings, storage, appName } = kibana.services; + + const { + uiSettings, + storage, + appName, + data, + usageCollection, + unifiedSearch, + notifications, + docLinks, + http, + } = kibana.services; + const isQueryLangSelected = props.query && !isOfQueryType(props.query); const queryLanguage = props.query && isOfQueryType(props.query) && props.query.language; @@ -404,7 +427,7 @@ export const QueryBarTopRow = React.memo( - )} diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx index 41060aaecb3df..b0dcdac7a421c 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx @@ -20,13 +20,12 @@ import { render } from '@testing-library/react'; import { EuiTextArea, EuiIcon } from '@elastic/eui'; -import { QueryLanguageSwitcher } from './language_switcher'; -import QueryStringInputUI from './query_string_input'; - import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; -import { KibanaContextProvider, withKibana } from '@kbn/kibana-react-plugin/public'; + +import { QueryLanguageSwitcher } from './language_switcher'; +import QueryStringInput from './query_string_input'; import { unifiedSearchPluginMock } from '../mocks'; jest.useFakeTimers(); @@ -63,27 +62,25 @@ const createMockStorage = () => ({ clear: jest.fn(), }); -const QueryStringInput = withKibana(QueryStringInputUI); - function wrapQueryStringInputInContext(testProps: any, storage?: any) { - const services = { - ...startMock, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - data: dataPluginMock.createStartContract(), - appName: testProps.appName || 'test', - storage: storage || createMockStorage(), - }; - const defaultOptions = { screenTitle: 'Another Screen', intl: null as any, + deps: { + unifiedSearch: unifiedSearchPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), + appName: testProps.appName || 'test', + storage: storage || createMockStorage(), + usageCollection: { reportUiCounter: () => {} }, + uiSettings: startMock.uiSettings, + http: startMock.http, + docLinks: startMock.docLinks, + }, }; return ( - - - + ); } @@ -200,7 +197,7 @@ describe('QueryStringInput', () => { }) ); - const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; + const instance = component.find('QueryStringInputUI').instance() as QueryStringInput; const input = instance.inputRef; const inputWrapper = component.find(EuiTextArea).find('textarea'); inputWrapper.simulate('keyDown', { target: input, keyCode: 13, key: 'Enter', metaKey: true }); @@ -341,7 +338,7 @@ describe('QueryStringInput', () => { }) ); - const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; + const instance = component.find('QueryStringInputUI').instance() as QueryStringInput; const input = instance.inputRef; const inputWrapper = component.find(EuiTextArea).find('textarea'); inputWrapper.simulate('keyDown', { target: input, keyCode: 13, key: 'Enter', metaKey: true }); @@ -379,7 +376,7 @@ describe('QueryStringInput', () => { }) ); - const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; + const instance = component.find('QueryStringInputUI').instance() as QueryStringInput; const input = instance.inputRef; const inputWrapper = component.find(EuiTextArea).find('textarea'); input!.value = 'foo\u00A0bar'; diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.tsx index 84a12d8c63200..235173dc4c23c 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.tsx @@ -28,27 +28,41 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { compact, debounce, isEmpty, isEqual, isFunction } from 'lodash'; -import { Toast } from '@kbn/core/public'; +import { CoreStart, DocLinksStart, Toast } from '@kbn/core/public'; import type { Query } from '@kbn/es-query'; -import { getQueryLog } from '@kbn/data-plugin/public'; +import { DataPublicPluginStart, getQueryLog } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; import type { PersistedLog } from '@kbn/data-plugin/public'; import { getFieldSubtypeNested, KIBANA_USER_QUERY_LANGUAGE_KEY } from '@kbn/data-plugin/common'; -import { KibanaReactContextValue, toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { matchPairs } from './match_pairs'; import { toUser } from './to_user'; import { fromUser } from './from_user'; import { fetchIndexPatterns } from './fetch_index_patterns'; import { QueryLanguageSwitcher } from './language_switcher'; import type { SuggestionsListSize } from '../typeahead/suggestions_component'; -import type { IUnifiedSearchPluginServices } from '../types'; import { SuggestionsComponent } from '../typeahead'; import { onRaf } from '../utils'; import { FilterButtonGroup } from '../filter_bar/filter_button_group/filter_button_group'; -import { QuerySuggestion, QuerySuggestionTypes } from '../autocomplete'; +import { AutocompleteService, QuerySuggestion, QuerySuggestionTypes } from '../autocomplete'; import { getTheme } from '../services'; import './query_string_input.scss'; +export interface QueryStringInputDependencies { + unifiedSearch: { + autocomplete: ReturnType; + }; + usageCollection?: UsageCollectionStart; + data: DataPublicPluginStart; + storage: IStorageWrapper; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + docLinks: DocLinksStart; + uiSettings: CoreStart['uiSettings']; +} + export interface QueryStringInputProps { indexPatterns: Array; query: Query; @@ -72,6 +86,8 @@ export interface QueryStringInputProps { isClearable?: boolean; iconType?: EuiIconProps['type']; isDisabled?: boolean; + appName: string; + deps: QueryStringInputDependencies; /** * @param nonKqlMode by default if language switch is enabled, user can switch between kql and lucene syntax mode @@ -93,10 +109,6 @@ export interface QueryStringInputProps { timeRangeForSuggestionsOverride?: boolean; } -interface Props extends QueryStringInputProps { - kibana: KibanaReactContextValue; -} - interface State { isSuggestionsVisible: boolean; index: number | null; @@ -126,7 +138,7 @@ const KEY_CODES = { // Needed for React.lazy // eslint-disable-next-line import/no-default-export -export default class QueryStringInputUI extends PureComponent { +export default class QueryStringInputUI extends PureComponent { static defaultProps = { storageKey: KIBANA_USER_QUERY_LANGUAGE_KEY, iconType: 'search', @@ -149,10 +161,10 @@ export default class QueryStringInputUI extends PureComponent { private persistedLog: PersistedLog | undefined; private abortController?: AbortController; private fetchIndexPatternsAbortController?: AbortController; - private services = this.props.kibana.services; - private reportUiCounter = this.services.usageCollection?.reportUiCounter.bind( - this.services.usageCollection, - this.services.appName + + private reportUiCounter = this.props.deps.usageCollection?.reportUiCounter.bind( + this.props.deps.usageCollection, + this.props.appName ); private componentIsUnmounting = false; @@ -181,7 +193,7 @@ export default class QueryStringInputUI extends PureComponent { const currentAbortController = this.fetchIndexPatternsAbortController; const objectPatternsFromStrings = (await fetchIndexPatterns( - this.services.data.indexPatterns, + this.props.deps.data.indexPatterns, stringPatterns )) as DataView[]; @@ -203,9 +215,9 @@ export default class QueryStringInputUI extends PureComponent { const queryString = this.getQueryString(); const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); - const hasQuerySuggestions = await this.services.unifiedSearch.autocomplete.hasQuerySuggestions( - language - ); + + const hasQuerySuggestions = + this.props.deps.unifiedSearch.autocomplete.hasQuerySuggestions(language); if ( !hasQuerySuggestions || @@ -226,7 +238,7 @@ export default class QueryStringInputUI extends PureComponent { if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); const suggestions = - (await this.services.unifiedSearch.autocomplete.getQuerySuggestions({ + (await this.props.deps.unifiedSearch.autocomplete.getQuerySuggestions({ language, indexPatterns, query: queryString, @@ -456,13 +468,13 @@ export default class QueryStringInputUI extends PureComponent { if ( subTypeNested && subTypeNested.nested && - !this.services.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut') + !this.props.deps.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut') ) { - const { notifications, docLinks } = this.services; + const { notifications, docLinks } = this.props.deps; const onKQLNestedQuerySyntaxInfoOptOut = (toast: Toast) => { - if (!this.services.storage) return; - this.services.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); + if (!this.props.deps.storage) return; + this.props.deps.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); notifications!.toasts.remove(toast); }; @@ -536,12 +548,12 @@ export default class QueryStringInputUI extends PureComponent { // Send telemetry info every time the user opts in or out of kuery // As a result it is important this function only ever gets called in the // UI component's change handler. - this.services.http.post('/api/kibana/kql_opt_in_stats', { + this.props.deps.http.post('/api/kibana/kql_opt_in_stats', { body: JSON.stringify({ opt_in: language === 'kuery' }), }); const storageKey = this.props.storageKey; - this.services.storage.set(storageKey!, language); + this.props.deps.storage.set(storageKey!, language); const newQuery = { query: '', language }; this.onChange(newQuery); @@ -597,10 +609,11 @@ export default class QueryStringInputUI extends PureComponent { }; private initPersistedLog = () => { - const { uiSettings, storage, appName } = this.services; + const { uiSettings } = this.props.deps; + const { appName } = this.props; this.persistedLog = this.props.persistedLog ? this.props.persistedLog - : getQueryLog(uiSettings, storage, appName, this.props.query.language); + : getQueryLog(uiSettings, this.props.deps.storage, appName, this.props.query.language); }; public onMouseEnterSuggestion = (suggestion: QuerySuggestion, index: number) => { @@ -622,7 +635,7 @@ export default class QueryStringInputUI extends PureComponent { window.addEventListener('resize', this.handleAutoHeight); } - public componentDidUpdate(prevProps: Props) { + public componentDidUpdate(prevProps: QueryStringInputProps) { const parsedQuery = fromUser(toUser(this.props.query.query)); if (!isEqual(this.props.query.query, parsedQuery)) { this.onChange({ ...this.props.query, query: parsedQuery }); @@ -721,6 +734,9 @@ export default class QueryStringInputUI extends PureComponent { anchorPosition={this.props.languageSwitcherPopoverAnchorPosition} onSelectLanguage={this.onSelectLanguage} nonKqlMode={this.props.nonKqlMode} + deps={{ + docLinks: this.props.deps.docLinks, + }} /> ); @@ -748,7 +764,7 @@ export default class QueryStringInputUI extends PureComponent { style={{ position: 'relative', width: '100%' }} aria-label={i18n.translate('unifiedSearch.query.queryBar.comboboxAriaLabel', { defaultMessage: 'Search and filter the {pageType} page', - values: { pageType: this.services.appName }, + values: { pageType: this.props.appName }, })} aria-haspopup="true" aria-expanded={this.state.isSuggestionsVisible} @@ -777,7 +793,7 @@ export default class QueryStringInputUI extends PureComponent { spellCheck={false} aria-label={i18n.translate('unifiedSearch.query.queryBar.searchInputAriaLabel', { defaultMessage: 'Start typing to search and filter the {pageType} page', - values: { pageType: this.services.appName }, + values: { pageType: this.props.appName }, })} aria-autocomplete="list" aria-controls={this.state.isSuggestionsVisible ? 'kbnTypeahead__items' : undefined} diff --git a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx index a4df4d0f1a76e..5c0dfda5d9b2c 100644 --- a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx @@ -193,6 +193,8 @@ export function createSearchBar({ showQueryBar={props.showQueryBar} showQueryInput={props.showQueryInput} showSaveQuery={props.showSaveQuery} + showSubmitButton={props.showSubmitButton} + submitButtonStyle={props.submitButtonStyle} isDisabled={props.isDisabled} screenTitle={props.screenTitle} indexPatterns={props.indexPatterns} diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index 8a6396d993973..b58c53dea73a0 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -26,7 +26,7 @@ import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { SavedQueryManagementList } from '../saved_query_management'; import { QueryBarMenu, QueryBarMenuProps } from '../query_string_input/query_bar_menu'; import type { DataViewPickerProps, OnSaveTextLanguageQueryProps } from '../dataview_picker'; -import QueryBarTopRow from '../query_string_input/query_bar_top_row'; +import QueryBarTopRow, { QueryBarTopRowProps } from '../query_string_input/query_bar_top_row'; import { FilterBar, FilterItems } from '../filter_bar'; import type { SuggestionsListSize } from '../typeahead/suggestions_component'; import { searchBarStyles } from './search_bar.styles'; @@ -93,6 +93,7 @@ export interface SearchBarOwnProps { textBasedLanguageModeErrors?: Error[]; onTextBasedSavedAndExit?: ({ onSave }: OnSaveTextLanguageQueryProps) => void; showSubmitButton?: boolean; + submitButtonStyle?: QueryBarTopRowProps['submitButtonStyle']; // defines size of suggestions query popover suggestionsSize?: SuggestionsListSize; isScreenshotMode?: boolean; @@ -544,6 +545,7 @@ class SearchBarUI extends C this.props.customSubmitButton ? this.props.customSubmitButton : undefined } showSubmitButton={this.props.showSubmitButton} + submitButtonStyle={this.props.submitButtonStyle} dataTestSubj={this.props.dataTestSubj} indicateNoData={this.props.indicateNoData} placeholder={this.props.placeholder} diff --git a/src/plugins/unified_search/public/types.ts b/src/plugins/unified_search/public/types.ts index 246fc87114db4..d079cd72edf81 100755 --- a/src/plugins/unified_search/public/types.ts +++ b/src/plugins/unified_search/public/types.ts @@ -13,7 +13,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { UsageCollectionSetup, UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { Query, AggregateQuery } from '@kbn/es-query'; -import { CoreStart } from '@kbn/core/public'; +import { CoreStart, DocLinksStart } from '@kbn/core/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import type { IndexPatternSelectProps, StatefulSearchBarProps } from '.'; @@ -84,6 +84,7 @@ export interface IUnifiedSearchPluginServices extends Partial { application: CoreStart['application']; http: CoreStart['http']; storage: IStorageWrapper; + docLinks: DocLinksStart; data: DataPublicPluginStart; usageCollection?: UsageCollectionStart; } diff --git a/src/plugins/vis_default_editor/public/components/controls/filter.tsx b/src/plugins/vis_default_editor/public/components/controls/filter.tsx index 95740cbaa9624..e1393e71b808d 100644 --- a/src/plugins/vis_default_editor/public/components/controls/filter.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/filter.tsx @@ -11,11 +11,12 @@ import { EuiForm, EuiButtonIcon, EuiFieldText, EuiFormRow, EuiSpacer } from '@el import { i18n } from '@kbn/i18n'; import type { Query } from '@kbn/es-query'; -import { IAggConfig, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { IAggConfig } from '@kbn/data-plugin/public'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { VisDefaultEditorKibanaServices } from '../../types'; interface FilterRowProps { id: string; arrayIndex: number; @@ -41,7 +42,19 @@ function FilterRow({ onChangeValue, onRemoveFilter, }: FilterRowProps) { - const { services } = useKibana<{ data: DataPublicPluginStart; appName: string }>(); + const { services } = useKibana(); + const { + data, + unifiedSearch, + usageCollection, + storage, + notifications, + http, + docLinks, + uiSettings, + appName, + } = services; + const [showCustomLabel, setShowCustomLabel] = useState(false); const filterLabel = i18n.translate('visDefaultEditor.controls.filters.filterLabel', { defaultMessage: 'Filter {index}', @@ -53,7 +66,7 @@ function FilterRow({ const onBlur = () => { if (value.query.length > 0) { // Store filter to the query log so that it is available in autocomplete. - services.data.query.addToQueryLog(services.appName, value); + data.query.addToQueryLog(appName, value); } }; @@ -103,6 +116,17 @@ function FilterRow({ bubbleSubmitEvent={true} languageSwitcherPopoverAnchorPosition="leftDown" size="s" + deps={{ + data, + unifiedSearch, + usageCollection, + storage, + notifications, + http, + docLinks, + uiSettings, + }} + appName={appName} /> {showCustomLabel ? ( diff --git a/src/plugins/vis_default_editor/public/types.ts b/src/plugins/vis_default_editor/public/types.ts index 68375151e5068..3e3d0fbed29f1 100644 --- a/src/plugins/vis_default_editor/public/types.ts +++ b/src/plugins/vis_default_editor/public/types.ts @@ -6,8 +6,22 @@ * Side Public License, v 1. */ -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { CoreStart, DocLinksStart } from '@kbn/core/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; export interface VisDefaultEditorKibanaServices { data: DataPublicPluginStart; + appName: string; + unifiedSearch: { + autocomplete: AutocompleteStart; + }; + usageCollection?: UsageCollectionStart; + storage: IStorageWrapper; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + docLinks: DocLinksStart; + uiSettings: CoreStart['uiSettings']; } diff --git a/src/plugins/vis_types/timeseries/kibana.json b/src/plugins/vis_types/timeseries/kibana.json index 9194743d3af6b..049bd6beffd6f 100644 --- a/src/plugins/vis_types/timeseries/kibana.json +++ b/src/plugins/vis_types/timeseries/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "inspector", "dataViews", "fieldFormats", "usageCollection"], + "requiredPlugins": ["charts", "data", "expressions", "visualizations", "inspector", "dataViews", "fieldFormats", "usageCollection", "unifiedSearch"], "optionalPlugins": ["home"], "requiredBundles": ["unifiedSearch", "kibanaUtils", "kibanaReact", "fieldFormats"], "owner": { diff --git a/src/plugins/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx b/src/plugins/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx index f83180dcea225..05330329105de 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx @@ -6,14 +6,15 @@ * Side Public License, v 1. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { QueryStringInput, QueryStringInputProps } from '@kbn/unified-search-plugin/public'; -import { CoreStartContext } from '../contexts/query_input_bar_context'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { IndexPatternValue } from '../../../common/types'; import { getDataViewsStart } from '../../services'; import { fetchIndexPattern, isStringTypeIndexPattern } from '../../../common/index_patterns_utils'; +import { TimeseriesVisDependencies } from '../../plugin'; type QueryBarWrapperProps = Pick & { indexPatterns: IndexPatternValue[]; @@ -30,7 +31,18 @@ export function QueryBarWrapper({ const dataViews = getDataViewsStart(); const [indexes, setIndexes] = useState([]); - const coreStartContext = useContext(CoreStartContext); + const kibana = useKibana(); + const { + appName, + unifiedSearch, + storage, + data, + notifications, + http, + docLinks, + uiSettings, + usageCollection, + } = kibana.services; useEffect(() => { async function fetchIndexes() { @@ -63,11 +75,21 @@ export function QueryBarWrapper({ return ( ); diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_editor.tsx b/src/plugins/vis_types/timeseries/public/application/components/vis_editor.tsx index 587c665c89041..10138f180ed28 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_editor.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_editor.tsx @@ -29,7 +29,7 @@ import { extractIndexPatternValues } from '../../../common/index_patterns_utils' import { TIME_RANGE_DATA_MODES, TIME_RANGE_MODE_KEY } from '../../../common/enums'; import { VisPicker } from './vis_picker'; import { fetchFields, VisFields } from '../lib/fetch_fields'; -import { getDataStart, getCoreStart } from '../../services'; +import { getDataStart, getCoreStart, getUnifiedSearchStart } from '../../services'; import type { TimeseriesVisParams } from '../../types'; import { UseIndexPatternModeCallout } from './use_index_patter_mode_callout'; @@ -189,6 +189,7 @@ export class VisEditor extends Component(null); +export const CoreStartContext = React.createContext({} as ICoreStartContext); export const CoreStartContextProvider = CoreStartContext.Provider; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts index c9e72040e26dd..5b92c0ab21668 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts @@ -33,7 +33,7 @@ const getConvertFnByType = (type: PANEL_TYPES) => { */ export const convertTSVBtoLensConfiguration = async (model: Panel, timeRange?: TimeRange) => { // Disables the option for not supported charts, for the string mode and for series with annotations - if (!model.use_kibana_indexes || (model.annotations && model.annotations.length > 0)) { + if (!model.use_kibana_indexes) { return null; } // Disables if model is invalid diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts index 656dc3a8c4290..f869ac64380c6 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts @@ -17,6 +17,12 @@ import { } from '../../convert'; import { getLayers } from './layers'; import { createPanel, createSeries } from '../../__mocks__'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; + +jest.mock('uuid', () => ({ + v4: () => 'test-id', +})); describe('getLayers', () => { const dataSourceLayers: Record = [ @@ -200,6 +206,84 @@ describe('getLayers', () => { const panelWithPercentileRankMetric = createPanel({ series: [createSeries({ metrics: percentileRankMetrics })], }); + const panelWithSingleAnnotation = createPanel({ + annotations: [ + { + fields: 'geo.src,host', + template: 'Security Error from {{geo.src}} on {{host}}', + query_string: { + query: 'tags:error AND tags:security', + language: 'lucene', + }, + id: 'ann1', + color: 'rgba(211,49,21,0.7)', + time_field: 'timestamp', + icon: 'fa-asterisk', + ignore_global_filters: 1, + ignore_panel_filters: 1, + hidden: true, + index_pattern: { + id: 'test', + }, + }, + ], + series: [createSeries({ metrics: staticValueMetric })], + }); + const panelWithMultiAnnotations = createPanel({ + annotations: [ + { + fields: 'geo.src,host', + template: 'Security Error from {{geo.src}} on {{host}}', + query_string: { + query: 'tags:error AND tags:security', + language: 'lucene', + }, + id: 'ann1', + color: 'rgba(211,49,21,0.7)', + time_field: 'timestamp', + icon: 'fa-asterisk', + ignore_global_filters: 1, + ignore_panel_filters: 1, + hidden: true, + index_pattern: { + id: 'test', + }, + }, + { + query_string: { + query: 'tags: error AND tags: security', + language: 'kql', + }, + id: 'ann2', + color: 'blue', + time_field: 'timestamp', + icon: 'error-icon', + ignore_global_filters: 0, // todo test ignore when PR is r + ignore_panel_filters: 0, + index_pattern: { + id: 'test', + }, + }, + { + fields: 'category.keyword,price', + template: 'Will be ignored', + query_string: { + query: 'category.keyword:*', + language: 'kql', + }, + id: 'ann3', + color: 'red', + time_field: 'order_date', + icon: undefined, + ignore_global_filters: 1, + ignore_panel_filters: 1, + index_pattern: { + id: 'test2', + }, + }, + ], + series: [createSeries({ metrics: staticValueMetric })], + }); test.each<[string, [Record, Panel], Array>]>([ [ @@ -282,7 +366,159 @@ describe('getLayers', () => { }, ], ], - ])('should return %s', (_, input, expected) => { - expect(getLayers(...input)).toEqual(expected.map(expect.objectContaining)); + [ + 'annotation layer gets correct params and converts color, extraFields and icons', + [dataSourceLayersWithStatic, panelWithSingleAnnotation], + [ + { + layerType: 'referenceLine', + accessors: ['column-id-1'], + layerId: 'test-layer-1', + yConfig: [ + { + forAccessor: 'column-id-1', + axisMode: 'right', + color: '#68BC00', + fill: 'below', + }, + ], + }, + { + layerId: 'test-id', + layerType: 'annotations', + ignoreGlobalFilters: true, + annotations: [ + { + color: '#D33115', + extraFields: ['geo.src'], + filter: { + language: 'lucene', + query: 'tags:error AND tags:security', + type: 'kibana_query', + }, + icon: 'asterisk', + id: 'ann1', + isHidden: true, + key: { + type: 'point_in_time', + }, + label: 'Event', + timeField: 'timestamp', + type: 'query', + }, + ], + indexPatternId: 'test', + }, + ], + ], + [ + 'multiple annotations with different data views create separate layers', + [dataSourceLayersWithStatic, panelWithMultiAnnotations], + [ + { + layerType: 'referenceLine', + accessors: ['column-id-1'], + layerId: 'test-layer-1', + yConfig: [ + { + forAccessor: 'column-id-1', + axisMode: 'right', + color: '#68BC00', + fill: 'below', + }, + ], + }, + { + layerId: 'test-id', + layerType: 'annotations', + ignoreGlobalFilters: true, + annotations: [ + { + color: '#D33115', + extraFields: ['geo.src'], + filter: { + language: 'lucene', + query: 'tags:error AND tags:security', + type: 'kibana_query', + }, + icon: 'asterisk', + id: 'ann1', + isHidden: true, + key: { + type: 'point_in_time', + }, + label: 'Event', + timeField: 'timestamp', + type: 'query', + }, + { + color: '#0000FF', + filter: { + language: 'kql', + query: 'tags: error AND tags: security', + type: 'kibana_query', + }, + icon: 'triangle', + id: 'ann2', + key: { + type: 'point_in_time', + }, + label: 'Event', + timeField: 'timestamp', + type: 'query', + }, + ], + indexPatternId: 'test', + }, + { + layerId: 'test-id', + layerType: 'annotations', + ignoreGlobalFilters: true, + annotations: [ + { + color: '#FF0000', + extraFields: ['category.keyword', 'price'], + filter: { + language: 'kql', + query: 'category.keyword:*', + type: 'kibana_query', + }, + icon: 'triangle', + id: 'ann3', + key: { + type: 'point_in_time', + }, + label: 'Event', + timeField: 'order_date', + type: 'query', + }, + ], + indexPatternId: 'test2', + }, + ], + ], + ])('should return %s', async (_, input, expected) => { + const layers = await getLayers(...input, indexPatternsService as DataViewsPublicPluginStart); + expect(layers).toEqual(expected.map(expect.objectContaining)); }); }); + +const mockedIndices = [ + { + id: 'test', + title: 'test', + getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }), + }, +] as unknown as DataView[]; + +const indexPatternsService = { + getDefault: jest.fn(() => Promise.resolve({ id: 'default', title: 'index' })), + get: jest.fn(() => Promise.resolve(mockedIndices[0])), + find: jest.fn((search: string, size: number) => { + if (size !== 1) { + // shouldn't request more than one data view since there is a significant performance penalty + throw new Error('trying to fetch too many data views'); + } + return Promise.resolve(mockedIndices || []); + }), +} as unknown as DataViewsPublicPluginStart; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts index 0f411cc9d8fe6..064471e9fcfaf 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts @@ -7,13 +7,23 @@ */ import { + EventAnnotationConfig, FillTypes, + XYAnnotationsLayerConfig, XYLayerConfig, YAxisMode, } from '@kbn/visualizations-plugin/common/convert_to_lens'; import { PaletteOutput } from '@kbn/coloring'; +import { v4 } from 'uuid'; +import { transparentize } from '@elastic/eui'; +import Color from 'color'; +import { euiLightVars } from '@kbn/ui-theme'; +import { groupBy } from 'lodash'; +import { DataViewsPublicPluginStart, DataView } from '@kbn/data-plugin/public/data_views'; +import { fetchIndexPattern } from '../../../../../common/index_patterns_utils'; +import { ICON_TYPES_MAP } from '../../../../application/visualizations/constants'; import { SUPPORTED_METRICS } from '../../metrics'; -import type { Metric, Panel } from '../../../../../common/types'; +import type { Annotation, Metric, Panel } from '../../../../../common/types'; import { getSeriesAgg } from '../../series'; import { isPercentileRanksColumnWithMeta, @@ -51,11 +61,16 @@ function getColor( return seriesColor; } -export const getLayers = ( +function nonNullable(value: T): value is NonNullable { + return value !== null && value !== undefined; +} + +export const getLayers = async ( dataSourceLayers: Record, - model: Panel -): XYLayerConfig[] => { - return Object.keys(dataSourceLayers).map((key) => { + model: Panel, + dataViews: DataViewsPublicPluginStart +): Promise => { + const nonAnnotationsLayers: XYLayerConfig[] = Object.keys(dataSourceLayers).map((key) => { const series = model.series[parseInt(key, 10)]; const { metrics, seriesAgg } = getSeriesAgg(series.metrics); const dataSourceLayer = dataSourceLayers[parseInt(key, 10)]; @@ -112,4 +127,77 @@ export const getLayers = ( }; } }); + if (!model.annotations || !model.annotations.length) { + return nonAnnotationsLayers; + } + + const annotationsByIndexPattern = groupBy( + model.annotations, + (a) => typeof a.index_pattern === 'object' && 'id' in a.index_pattern && a.index_pattern.id + ); + + const annotationsLayers: Array = await Promise.all( + Object.entries(annotationsByIndexPattern).map(async ([indexPatternId, annotations]) => { + const convertedAnnotations: EventAnnotationConfig[] = []; + const { indexPattern } = (await fetchIndexPattern({ id: indexPatternId }, dataViews)) || {}; + + if (indexPattern) { + annotations.forEach((a: Annotation) => { + const lensAnnotation = convertAnnotation(a, indexPattern); + if (lensAnnotation) { + convertedAnnotations.push(lensAnnotation); + } + }); + return { + layerId: v4(), + layerType: 'annotations', + ignoreGlobalFilters: true, + annotations: convertedAnnotations, + indexPatternId, + }; + } + }) + ); + + return nonAnnotationsLayers.concat(...annotationsLayers.filter(nonNullable)); +}; + +const convertAnnotation = ( + annotation: Annotation, + dataView: DataView +): EventAnnotationConfig | undefined => { + if (annotation.query_string) { + const extraFields = annotation.fields + ? annotation.fields + ?.replace(/\s/g, '') + ?.split(',') + .map((field) => { + const dataViewField = dataView.getFieldByName(field); + return dataViewField && dataViewField.aggregatable ? field : undefined; + }) + .filter(nonNullable) + : undefined; + return { + type: 'query', + id: annotation.id, + label: 'Event', + key: { + type: 'point_in_time', + }, + color: new Color(transparentize(annotation.color || euiLightVars.euiColorAccent, 1)).hex(), + timeField: annotation.time_field, + icon: + annotation.icon && + ICON_TYPES_MAP[annotation.icon] && + typeof ICON_TYPES_MAP[annotation.icon] === 'string' + ? ICON_TYPES_MAP[annotation.icon] + : 'triangle', + filter: { + type: 'kibana_query', + ...annotation.query_string, + }, + extraFields, + isHidden: annotation.hidden, + }; + } }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts index c3f5900b35eb3..25cd1fa962d0d 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts @@ -9,6 +9,7 @@ import { parseTimeShift } from '@kbn/data-plugin/common'; import { Layer } from '@kbn/visualizations-plugin/common/convert_to_lens'; import uuid from 'uuid'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { Panel } from '../../../common/types'; import { PANEL_TYPES } from '../../../common/enums'; import { getDataViewsStart } from '../../services'; @@ -37,7 +38,7 @@ const excludeMetaFromLayers = (layers: Record): Record { - const dataViews = getDataViewsStart(); + const dataViews: DataViewsPublicPluginStart = getDataViewsStart(); const extendedLayers: Record = {}; const seriesNum = model.series.filter((series) => !series.hidden).length; @@ -96,9 +97,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel }; } + const configLayers = await getLayers(extendedLayers, model, dataViews); + return { type: 'lnsXY', layers: Object.values(excludeMetaFromLayers(extendedLayers)), - configuration: getConfiguration(model, getLayers(extendedLayers, model)), + configuration: getConfiguration(model, configLayers), }; }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts index 1afbd89e7027f..2622fadf5e753 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts @@ -79,9 +79,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR }; } + const configLayers = await getLayers(extendedLayers, model, dataViews); + return { type: 'lnsXY', layers: Object.values(excludeMetaFromLayers(extendedLayers)), - configuration: getConfiguration(model, getLayers(extendedLayers, model)), + configuration: getConfiguration(model, configLayers), }; }; diff --git a/src/plugins/vis_types/timeseries/public/plugin.ts b/src/plugins/vis_types/timeseries/public/plugin.ts index 8ca5a916a7ce3..6054a0dcd3d3b 100644 --- a/src/plugins/vis_types/timeseries/public/plugin.ts +++ b/src/plugins/vis_types/timeseries/public/plugin.ts @@ -9,11 +9,17 @@ import type { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public'; import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataPublicPluginStart, TimefilterContract } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { IUiSettingsClient } from '@kbn/core/public'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { EditorController, TSVB_EDITOR_NAME } from './application/editor_controller'; @@ -28,6 +34,7 @@ import { setDataViewsStart, setCharts, setUsageCollectionStart, + setUnifiedSearchStart, } from './services'; import { getTimeseriesVisRenderer } from './timeseries_vis_renderer'; @@ -44,6 +51,22 @@ export interface MetricsPluginStartDependencies { dataViews: DataViewsPublicPluginStart; charts: ChartsPluginStart; usageCollection: UsageCollectionStart; + unifiedSearch: UnifiedSearchPublicPluginStart; +} + +/** @internal */ +export interface TimeseriesVisDependencies extends Partial { + uiSettings: IUiSettingsClient; + http: HttpSetup; + timefilter: TimefilterContract; + theme: ThemeServiceStart; + appName: string; + unifiedSearch: UnifiedSearchPublicPluginStart; + notifications: CoreStart['notifications']; + storage: IStorageWrapper; + data: DataPublicPluginStart; + usageCollection?: UsageCollectionStart; + docLinks: DocLinksStart; } /** @internal */ @@ -69,12 +92,20 @@ export class MetricsPlugin implements Plugin { public start( core: CoreStart, - { data, charts, dataViews, usageCollection, fieldFormats }: MetricsPluginStartDependencies + { + data, + charts, + dataViews, + usageCollection, + fieldFormats, + unifiedSearch, + }: MetricsPluginStartDependencies ) { setCharts(charts); setI18n(core.i18n); setFieldFormats(fieldFormats); setDataStart(data); + setUnifiedSearchStart(unifiedSearch); setDataViewsStart(dataViews); setCoreStart(core); setUsageCollectionStart(usageCollection); diff --git a/src/plugins/vis_types/timeseries/public/services.ts b/src/plugins/vis_types/timeseries/public/services.ts index c68971e24822b..d7e5777361b83 100644 --- a/src/plugins/vis_types/timeseries/public/services.ts +++ b/src/plugins/vis_types/timeseries/public/services.ts @@ -13,6 +13,7 @@ import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); @@ -22,6 +23,8 @@ export const [getFieldFormats, setFieldFormats] = export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); export const [getDataStart, setDataStart] = createGetterSetter('DataStart'); +export const [getUnifiedSearchStart, setUnifiedSearchStart] = + createGetterSetter('unifiedSearchStart'); export const [getDataViewsStart, setDataViewsStart] = createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_types/timeseries/server/lib/get_vis_data.ts index 360256ccb628b..9854472c1dc3f 100644 --- a/src/plugins/vis_types/timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_types/timeseries/server/lib/get_vis_data.ts @@ -8,6 +8,8 @@ import _ from 'lodash'; +import { validateField } from '../../common/fields_utils'; +import { TimeFieldNotSpecifiedError } from '../../common/errors'; import { Framework } from '../plugin'; import type { TimeseriesVisData, FetchedIndexPattern, Series } from '../../common/types'; import { PANEL_TYPES } from '../../common/enums'; @@ -20,7 +22,7 @@ import { getSeriesData } from './vis_data/get_series_data'; import { getTableData } from './vis_data/get_table_data'; import { getEsQueryConfig } from './vis_data/helpers/get_es_query_uisettings'; import { getCachedIndexPatternFetcher } from './search_strategies/lib/cached_index_pattern_fetcher'; -import { getIntervalAndTimefield } from './vis_data/get_interval_and_timefield'; +import { getInterval } from './vis_data/get_interval'; import { UI_SETTINGS } from '../../common/constants'; export async function getVisData( @@ -60,16 +62,40 @@ export async function getVisData( const maxBuckets = await uiSettings.get(UI_SETTINGS.MAX_BUCKETS_SETTING); const { min, max } = request.body.timerange; - return getIntervalAndTimefield( - panel, - index, - { - min, - max, - maxBuckets, - }, - series - ); + let timeField = + (series?.override_index_pattern ? series.series_time_field : panel.time_field) || + index.indexPattern?.timeFieldName; + + /** This code is historically in TSVB and for backward compatibility + * we cannot remove it while we support String Indexes. + * Case: only for String Indexes mode + if user doesn't provide timeField + * we should use @timestamp as default timeField **/ + if (!panel.use_kibana_indexes && !timeField) { + timeField = '@timestamp'; + } + + if (panel.use_kibana_indexes) { + if (timeField) { + validateField(timeField, index); + } else { + throw new TimeFieldNotSpecifiedError(); + } + } + + return { + timeField, + ...getInterval( + timeField!, + panel, + index, + { + min, + max, + maxBuckets, + }, + series + ), + }; }, }; diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/annotations/get_request_params.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/annotations/get_request_params.ts index 815598007030d..f86ec121683a9 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/annotations/get_request_params.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/annotations/get_request_params.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ import { i18n } from '@kbn/i18n'; +import { getInterval } from '../get_interval'; +import { UI_SETTINGS } from '../../../../common/constants'; import type { Annotation, Panel } from '../../../../common/types'; import { buildAnnotationRequest } from './build_request_body'; import type { @@ -31,7 +33,6 @@ export async function getAnnotationRequestParams( capabilities, uiSettings, cachedIndexPatternFetcher, - buildSeriesMetaParams, }: AnnotationServices ): Promise { const annotationIndex = await cachedIndexPatternFetcher(annotation.index_pattern); @@ -44,7 +45,21 @@ export async function getAnnotationRequestParams( annotationIndex, capabilities, uiSettings, - getMetaParams: () => buildSeriesMetaParams(annotationIndex, Boolean(panel.use_kibana_indexes)), + getMetaParams: async () => { + const maxBuckets = await uiSettings.get(UI_SETTINGS.MAX_BUCKETS_SETTING); + const { min, max } = req.body.timerange; + const timeField = + annotation.time_field ?? annotationIndex.indexPattern?.timeFieldName ?? panel.time_field; + + return { + timeField, + ...getInterval(timeField!, panel, annotationIndex, { + min, + max, + maxBuckets, + }), + }; + }, }); return { diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.test.ts similarity index 69% rename from src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts rename to src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.test.ts index 45e0b410b6a6b..bcd3ea99e4f21 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.test.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { getIntervalAndTimefield } from './get_interval_and_timefield'; +import { getInterval } from './get_interval'; import { FetchedIndexPattern, Panel, Series } from '../../../common/types'; -describe('getIntervalAndTimefield(panel, series)', () => { +describe('getInterval', () => { const index: FetchedIndexPattern = {} as FetchedIndexPattern; const params = { min: '2017-01-01T00:00:00Z', @@ -17,17 +17,16 @@ describe('getIntervalAndTimefield(panel, series)', () => { maxBuckets: 1000, }; - test('returns the panel interval and timefield', () => { + test('returns the panel interval', () => { const panel = { time_field: '@timestamp', interval: 'auto' } as Panel; const series = {} as Series; - expect(getIntervalAndTimefield(panel, index, params, series)).toEqual({ - timeField: '@timestamp', + expect(getInterval('@timestamp', panel, index, params, series)).toEqual({ interval: 'auto', }); }); - test('returns the series interval and timefield', () => { + test('returns the series interval', () => { const panel = { time_field: '@timestamp', interval: 'auto' } as Panel; const series = { override_index_pattern: true, @@ -35,8 +34,7 @@ describe('getIntervalAndTimefield(panel, series)', () => { series_time_field: 'time', } as unknown as Series; - expect(getIntervalAndTimefield(panel, index, params, series)).toEqual({ - timeField: 'time', + expect(getInterval('@timestamp', panel, index, params, series)).toEqual({ interval: '1m', }); }); diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.ts similarity index 63% rename from src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.ts rename to src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.ts index af6eb44affabc..593eb30b7a9aa 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval_and_timefield.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_interval.ts @@ -5,11 +5,10 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import moment from 'moment'; import { AUTO_INTERVAL } from '../../../common/constants'; -import { validateField } from '../../../common/fields_utils'; import { validateInterval } from '../../../common/validate_interval'; -import { TimeFieldNotSpecifiedError } from '../../../common/errors'; import type { FetchedIndexPattern, Panel, Series } from '../../../common/types'; @@ -19,29 +18,13 @@ interface IntervalParams { maxBuckets: number; } -export function getIntervalAndTimefield( +export function getInterval( + timeField: string, panel: Panel, index: FetchedIndexPattern, { min, max, maxBuckets }: IntervalParams, series?: Series ) { - let timeField = - (series?.override_index_pattern ? series.series_time_field : panel.time_field) || - index.indexPattern?.timeFieldName; - - // should use @timestamp as default timeField for es indeces if user doesn't provide timeField - if (!panel.use_kibana_indexes && !timeField) { - timeField = '@timestamp'; - } - - if (panel.use_kibana_indexes) { - if (timeField) { - validateField(timeField, index); - } else { - throw new TimeFieldNotSpecifiedError(); - } - } - let interval = panel.interval; let maxBars = panel.max_bars; @@ -61,7 +44,6 @@ export function getIntervalAndTimefield( return { maxBars, - timeField, interval: interval || AUTO_INTERVAL, }; } diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index ece2b2d316eda..debe2b5cbdf19 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -8,7 +8,7 @@ import { DefaultSearchCapabilities } from '../../../search_strategies/capabilities/default_search_capabilities'; import { dateHistogram } from './date_histogram'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; +import { getInterval } from '../../get_interval'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; describe('dateHistogram(req, panel, series)', () => { @@ -46,8 +46,10 @@ describe('dateHistogram(req, panel, series)', () => { uiSettings = { get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50), }; - buildSeriesMetaParams = jest.fn(async () => { - return getIntervalAndTimefield( + buildSeriesMetaParams = jest.fn(async () => ({ + timeField: '@timestamp', + ...getInterval( + '@timestamp', panel, indexPattern, { @@ -56,8 +58,8 @@ describe('dateHistogram(req, panel, series)', () => { maxBuckets: 1000, }, series - ); - }); + ), + })); }); test('calls next when finished', async () => { @@ -163,7 +165,7 @@ describe('dateHistogram(req, panel, series)', () => { test('returns valid date histogram with overridden index pattern', async () => { series.override_index_pattern = 1; series.series_index_pattern = '*'; - series.series_time_field = 'timestamp'; + series.series_time_field = '@timestamp'; series.series_interval = '20s'; const next = (doc) => doc; const doc = await dateHistogram( @@ -183,7 +185,7 @@ describe('dateHistogram(req, panel, series)', () => { aggs: { timeseries: { date_histogram: { - field: 'timestamp', + field: '@timestamp', fixed_interval: '20s', min_doc_count: 0, time_zone: 'UTC', @@ -196,7 +198,7 @@ describe('dateHistogram(req, panel, series)', () => { }, meta: { intervalString: '20s', - timeField: 'timestamp', + timeField: '@timestamp', seriesId: 'test', panelId: 'panelId', }, diff --git a/src/plugins/visualizations/common/convert_to_lens/index.ts b/src/plugins/visualizations/common/convert_to_lens/index.ts index f47605f92719d..d336872cff6bb 100644 --- a/src/plugins/visualizations/common/convert_to_lens/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/index.ts @@ -8,3 +8,4 @@ export * from './types'; export * from './constants'; +export * from './utils'; 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 b908ee2c5346b..4702ebae80826 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -9,6 +9,7 @@ import { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts'; import { $Values } from '@kbn/utility-types'; import type { PaletteOutput } from '@kbn/coloring'; +import { KibanaQueryOutput } from '@kbn/data-plugin/common'; import { LegendSize } from '../../constants'; export const XYCurveTypes = { @@ -90,7 +91,33 @@ export interface XYReferenceLineLayerConfig { layerType: 'referenceLine'; } -export type XYLayerConfig = XYDataLayerConfig | XYReferenceLineLayerConfig; +export interface EventAnnotationConfig { + id: string; + filter: KibanaQueryOutput; + timeField?: string; + extraFields?: string[]; + label: string; + color?: string; + isHidden?: boolean; + icon?: string; + type: 'query'; + key: { + type: 'point_in_time'; + }; +} + +export interface XYAnnotationsLayerConfig { + layerId: string; + annotations: EventAnnotationConfig[]; + ignoreGlobalFilters: boolean; + layerType: 'annotations'; + indexPatternId: string; +} + +export type XYLayerConfig = + | XYDataLayerConfig + | XYReferenceLineLayerConfig + | XYAnnotationsLayerConfig; export interface AxesSettingsConfig { x: boolean; diff --git a/src/plugins/visualizations/common/convert_to_lens/utils.ts b/src/plugins/visualizations/common/convert_to_lens/utils.ts new file mode 100644 index 0000000000000..1beba70bc2a1b --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/utils.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 { XYAnnotationsLayerConfig, XYLayerConfig } from './types'; + +export const isAnnotationsLayer = ( + layer: Pick +): layer is XYAnnotationsLayerConfig => layer.layerType === 'annotations'; diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json index 7a36f94061159..fc35feb51039c 100644 --- a/src/plugins/visualizations/kibana.json +++ b/src/plugins/visualizations/kibana.json @@ -17,7 +17,8 @@ "screenshotMode", "presentationUtil", "dataViews", - "dataViewEditor" + "dataViewEditor", + "unifiedSearch" ], "optionalPlugins": ["home", "share", "spaces", "savedObjectsTaggingOss"], "requiredBundles": ["kibanaUtils", "savedSearch", "kibanaReact", "charts"], diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index 49a083647fbf8..e6d0e001f4572 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -23,6 +23,7 @@ import { presentationUtilPluginMock } from '@kbn/presentation-util-plugin/public import { savedObjectTaggingOssPluginMock } from '@kbn/saved-objects-tagging-oss-plugin/public/mocks'; import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { VisualizationsPlugin } from './plugin'; import { Schemas } from './vis_types'; import { Schema, VisualizationsSetup, VisualizationsStart } from '.'; @@ -75,6 +76,7 @@ const createInstance = async () => { urlForwarding: urlForwardingPluginMock.createStartContract(), screenshotMode: screenshotModePluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), + unifiedSearch: unifiedSearchPluginMock.createStartContract(), }); return { diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index e98ba20fe3056..50245642e7fed 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -25,6 +25,8 @@ import { withNotifyOnErrors, } from '@kbn/kibana-utils-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; + import type { PluginInitializerContext, CoreSetup, @@ -135,6 +137,7 @@ export interface VisualizationsStartDeps { urlForwarding: UrlForwardingStart; screenshotMode: ScreenshotModePluginStart; fieldFormats: FieldFormatsStart; + unifiedSearch: UnifiedSearchPublicPluginStart; } /** @@ -287,6 +290,7 @@ export class VisualizationsPlugin getKibanaVersion: () => this.initializerContext.env.packageInfo.version, spaces: pluginsStart.spaces, visEditorsRegistry, + unifiedSearch: pluginsStart.unifiedSearch, }; params.element.classList.add('visAppWrapper'); diff --git a/src/plugins/visualizations/public/visualize_app/types.ts b/src/plugins/visualizations/public/visualize_app/types.ts index 88672430c2550..8fe5ad4a60775 100644 --- a/src/plugins/visualizations/public/visualize_app/types.ts +++ b/src/plugins/visualizations/public/visualize_app/types.ts @@ -10,6 +10,8 @@ import type { EventEmitter } from 'events'; import type { History } from 'history'; import type { SerializableRecord } from '@kbn/utility-types'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; + import type { CoreStart, PluginInitializerContext, @@ -110,6 +112,7 @@ export interface VisualizeServices extends CoreStart { spaces?: SpacesPluginStart; theme: ThemeServiceStart; visEditorsRegistry: VisEditorsRegistry; + unifiedSearch: UnifiedSearchPublicPluginStart; } export interface VisInstance { @@ -143,6 +146,7 @@ export interface EditorRenderProps { query?: Query; savedSearch?: SavedSearch; uiState: PersistedState; + unifiedSearch: UnifiedSearchPublicPluginStart; /** * Flag to determine if visualiztion is linked to the saved search */ diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.ts index 4f7245ed436d7..1492d628d5425 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.ts @@ -54,6 +54,7 @@ export const useEditorUpdates = ( query: queryString.getQuery() as Query, linked: !!vis.data.savedSearchId, savedSearch, + unifiedSearch: services.unifiedSearch, }); } else { embeddableHandler.updateInput({ diff --git a/test/api_integration/apis/saved_objects/bulk_delete.ts b/test/api_integration/apis/saved_objects/bulk_delete.ts new file mode 100644 index 0000000000000..5b5292b97ddde --- /dev/null +++ b/test/api_integration/apis/saved_objects/bulk_delete.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + + describe('bulk_delete', () => { + before(async () => { + 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 when deleting many docs', async () => + await supertest + .post(`/api/saved_objects/_bulk_delete`) + .send([ + { + type: 'visualization', + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + }, + { + type: 'dashboard', + id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', + }, + ]) + .expect(200) + .then((resp) => { + expect(resp.body).to.eql({ + statuses: [ + { + success: true, + type: 'visualization', + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + }, + { + success: true, + type: 'dashboard', + id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', + }, + ], + }); + })); + + it('should return generic 404 when deleting an unknown doc', async () => + await supertest + .post(`/api/saved_objects/_bulk_delete`) + .send([{ type: 'dashboard', id: 'not-a-real-id' }]) + .expect(200) + .then((resp) => { + expect(resp.body).to.eql({ + statuses: [ + { + error: { + error: 'Not Found', + message: 'Saved object [dashboard/not-a-real-id] not found', + statusCode: 404, + }, + id: 'not-a-real-id', + type: 'dashboard', + success: false, + }, + ], + }); + })); + + it('should return the result of deleting valid and invalid objects in the same request', async () => + await supertest + .post(`/api/saved_objects/_bulk_delete`) + .send([ + { type: 'visualization', id: 'not-a-real-vis-id' }, + { + type: 'index-pattern', + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + }, + ]) + .expect(200) + .then((resp) => { + expect(resp.body).to.eql({ + statuses: [ + { + error: { + error: 'Not Found', + message: 'Saved object [visualization/not-a-real-vis-id] not found', + statusCode: 404, + }, + id: 'not-a-real-vis-id', + type: 'visualization', + success: false, + }, + { + success: true, + type: 'index-pattern', + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + }, + ], + }); + })); + }); +} diff --git a/test/api_integration/apis/saved_objects/index.ts b/test/api_integration/apis/saved_objects/index.ts index 44ee3d8d7d76b..c981add4540b3 100644 --- a/test/api_integration/apis/saved_objects/index.ts +++ b/test/api_integration/apis/saved_objects/index.ts @@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('saved_objects', () => { loadTestFile(require.resolve('./bulk_create')); + loadTestFile(require.resolve('./bulk_delete')); loadTestFile(require.resolve('./bulk_get')); loadTestFile(require.resolve('./bulk_update')); loadTestFile(require.resolve('./create')); diff --git a/test/common/services/bsearch.ts b/test/common/services/bsearch.ts index 3c8a58c46867b..2a0278840a7c8 100644 --- a/test/common/services/bsearch.ts +++ b/test/common/services/bsearch.ts @@ -10,8 +10,7 @@ import expect from '@kbn/expect'; import request from 'superagent'; import type SuperTest from 'supertest'; import { IEsSearchResponse } from '@kbn/data-plugin/common'; -import { FtrProviderContext } from '../ftr_provider_context'; -import { RetryService } from './retry/retry'; +import { FtrService } from '../ftr_provider_context'; /** * Function copied from here: @@ -46,9 +45,8 @@ interface SendOptions { } /** - * Bsearch factory which will return a new bsearch capable service that can reduce flake - * on the CI systems when they are under pressure and bsearch returns an async search - * response or a sync response. + * Bsearch Service that can reduce flake on the CI systems when they are under + * pressure and bsearch returns an async search response or a sync response. * * @example * const supertest = getService('supertest'); @@ -57,21 +55,18 @@ interface SendOptions { * supertest, * options: { * defaultIndex: ['large_volume_dns_data'], - * } - * strategy: 'securitySolutionSearchStrategy', - * }); + * }, + * strategy: 'securitySolutionSearchStrategy', + * }); * expect(response).eql({ ... your value ... }); */ -export const BSearchFactory = (retry: RetryService) => ({ +export class BsearchService extends FtrService { + private readonly retry = this.ctx.getService('retry'); + /** Send method to send in your supertest, url, options, and strategy name */ - send: async ({ - supertest, - options, - strategy, - space, - }: SendOptions): Promise => { + async send({ supertest, options, strategy, space }: SendOptions) { const spaceUrl = getSpaceUrlPrefix(space); - const { body } = await retry.try(async () => { + const { body } = await this.retry.try(async () => { return supertest .post(`${spaceUrl}/internal/search/${strategy}`) .set('kbn-xsrf', 'true') @@ -79,44 +74,32 @@ export const BSearchFactory = (retry: RetryService) => ({ .expect(200); }); - if (body.isRunning) { - const result = await retry.try(async () => { - const resp = await supertest - .post(`${spaceUrl}/internal/bsearch`) - .set('kbn-xsrf', 'true') - .send({ - batch: [ - { - request: { - id: body.id, - ...options, - }, - options: { - strategy, - }, - }, - ], - }) - .expect(200); - const [parsedResponse] = parseBfetchResponse(resp); - expect(parsedResponse.result.isRunning).equal(false); - return parsedResponse.result; - }); - return result; - } else { + if (!body.isRunning) { return body; } - }, -}); -/** - * Bsearch provider which will return a new bsearch capable service that can reduce flake - * on the CI systems when they are under pressure and bsearch returns an async search response - * or a sync response. - */ -export function BSearchProvider({ - getService, -}: FtrProviderContext): ReturnType { - const retry = getService('retry'); - return BSearchFactory(retry); + const result = await this.retry.try(async () => { + const resp = await supertest + .post(`${spaceUrl}/internal/bsearch`) + .set('kbn-xsrf', 'true') + .send({ + batch: [ + { + request: { + id: body.id, + ...options, + }, + options: { + strategy, + }, + }, + ], + }) + .expect(200); + const [parsedResponse] = parseBfetchResponse(resp); + expect(parsedResponse.result.isRunning).equal(false); + return parsedResponse.result as T; + }); + return result; + } } diff --git a/test/common/services/index.ts b/test/common/services/index.ts index 91d17ce1bb3e8..25c8ea0cdd27d 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -6,28 +6,22 @@ * Side Public License, v 1. */ +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { DeploymentService } from './deployment'; -import { ElasticsearchProvider } from './elasticsearch'; -import { EsArchiverProvider } from './es_archiver'; -import { KibanaServerProvider } from './kibana_server'; -import { RetryService } from './retry'; import { RandomnessService } from './randomness'; import { SecurityServiceProvider } from './security'; import { EsDeleteAllIndicesProvider } from './es_delete_all_indices'; import { SavedObjectInfoService } from './saved_object_info'; import { IndexPatternsService } from './index_patterns'; -import { BSearchProvider } from './bsearch'; +import { BsearchService } from './bsearch'; export const services = { + ...commonFunctionalServices, deployment: DeploymentService, - es: ElasticsearchProvider, - esArchiver: EsArchiverProvider, - kibanaServer: KibanaServerProvider, - retry: RetryService, randomness: RandomnessService, security: SecurityServiceProvider, esDeleteAllIndices: EsDeleteAllIndicesProvider, savedObjectInfo: SavedObjectInfoService, indexPatterns: IndexPatternsService, - bsearch: BSearchProvider, + bsearch: BsearchService, }; diff --git a/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts b/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts index bd47c072e7735..8b9a31e2093c0 100644 --- a/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts +++ b/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts @@ -78,8 +78,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.checkCurrentRowsPerPageToBe(10); }); - it('should render duplicate saved search embeddables', async () => { + it('should control columns correctly', async () => { await PageObjects.dashboard.switchToEditMode(); + + const cell = await dataGrid.getCellElement(0, 2); + expect(await cell.getVisibleText()).to.be('Sep 22, 2015 @ 23:50:13.253'); + await dataGrid.clickMoveColumnLeft('agent'); + + const cellAfter = await dataGrid.getCellElement(0, 2); + expect(await cellAfter.getVisibleText()).to.be( + 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)' + ); + + await dataGrid.clickRemoveColumn('agent'); + expect(await cell.getVisibleText()).to.be('Sep 22, 2015 @ 23:50:13.253'); + }); + + it('should render duplicate saved search embeddables', async () => { await addSearchEmbeddableToDashboard(); const [firstGridCell, secondGridCell] = await dataGrid.getAllCellElements(); const firstGridCellContent = await firstGridCell.getVisibleText(); diff --git a/test/functional/services/common/failure_debugging.ts b/test/functional/services/common/failure_debugging.ts index 343036436293d..5555ae78bccf8 100644 --- a/test/functional/services/common/failure_debugging.ts +++ b/test/functional/services/common/failure_debugging.ts @@ -9,9 +9,9 @@ import { resolve } from 'path'; import { writeFile, mkdir } from 'fs'; import { promisify } from 'util'; -import { createHash } from 'crypto'; import del from 'del'; +import { FtrScreenshotFilename } from '@kbn/ftr-screenshot-filename'; import { FtrProviderContext } from '../../ftr_provider_context'; interface Test { @@ -49,15 +49,7 @@ export async function FailureDebuggingProvider({ getService }: FtrProviderContex } async function onFailure(_: any, test: Test) { - const fullName = test.fullTitle(); - - // include a hash of the full title of the test in the filename so that even with truncation filenames are - // always unique and deterministic based on the test title - const hash = createHash('sha256').update(fullName).digest('hex'); - - // Replace characters in test names which can't be used in filenames, like * - const name = `${fullName.replace(/([^ a-zA-Z0-9-]+)/g, '_').slice(0, 80)}-${hash}`; - + const name = FtrScreenshotFilename.create(test.fullTitle(), { ext: false }); await Promise.all([screenshots.takeForFailure(name), logCurrentUrl(), savePageHtml(name)]); } diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 68b2553478df7..c3107188d7c56 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -254,30 +254,36 @@ export class DataGridService extends FtrService { }); } - public async clickDocSortAsc(field?: string, sortText = 'Sort Old-New') { + private async clickColumnMenuField(field?: string) { if (field) { await this.openColMenuByField(field); } else { await this.find.clickByCssSelector('.euiDataGridHeaderCell__button'); } + } + + public async clickDocSortAsc(field?: string, sortText = 'Sort Old-New') { + await this.clickColumnMenuField(field); await this.find.clickByButtonText(sortText); } public async clickDocSortDesc(field?: string, sortText = 'Sort New-Old') { - if (field) { - await this.openColMenuByField(field); - } else { - await this.find.clickByCssSelector('.euiDataGridHeaderCell__button'); - } + await this.clickColumnMenuField(field); await this.find.clickByButtonText(sortText); } + public async clickMoveColumnRight(field?: string) { + await this.clickColumnMenuField(field); + await this.find.clickByButtonText('Move right'); + } + + public async clickMoveColumnLeft(field?: string) { + await this.clickColumnMenuField(field); + await this.find.clickByButtonText('Move left'); + } + public async clickRemoveColumn(field?: string) { - if (field) { - await this.openColMenuByField(field); - } else { - await this.find.clickByCssSelector('.euiDataGridHeaderCell__button'); - } + await this.clickColumnMenuField(field); await this.find.clickByButtonText('Remove column'); } diff --git a/tsconfig.base.json b/tsconfig.base.json index 9f04a4d6a1e4e..b62beb6650448 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -313,6 +313,8 @@ "@kbn/canvas-plugin/*": ["x-pack/plugins/canvas/*"], "@kbn/cases-plugin": ["x-pack/plugins/cases"], "@kbn/cases-plugin/*": ["x-pack/plugins/cases/*"], + "@kbn/cloud-experiments-plugin": ["x-pack/plugins/cloud_integrations/cloud_experiments"], + "@kbn/cloud-experiments-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_experiments/*"], "@kbn/cloud-security-posture-plugin": ["x-pack/plugins/cloud_security_posture"], "@kbn/cloud-security-posture-plugin/*": ["x-pack/plugins/cloud_security_posture/*"], "@kbn/cloud-plugin": ["x-pack/plugins/cloud"], diff --git a/versions.json b/versions.json index 11776f57850ee..a39a1412c46f6 100644 --- a/versions.json +++ b/versions.json @@ -2,13 +2,19 @@ "notice": "This file is not maintained outside of the main branch and should only be used for tooling.", "versions": [ { - "version": "8.5.0", + "version": "8.6.0", "branch": "main", "currentMajor": true, "currentMinor": true }, { - "version": "8.4.2", + "version": "8.5.0", + "branch": "8.5", + "currentMajor": true, + "previousMinor": true + }, + { + "version": "8.4.3", "branch": "8.4", "currentMajor": true, "previousMinor": true diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 8c1b4ce3daa86..83466ba749605 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -18,6 +18,7 @@ "xpack.endpoint": "plugins/endpoint", "xpack.enterpriseSearch": "plugins/enterprise_search", "xpack.features": "plugins/features", + "xpack.files": "plugins/files", "xpack.dataVisualizer": "plugins/data_visualizer", "xpack.fileUpload": "plugins/file_upload", "xpack.globalSearch": ["plugins/global_search"], @@ -38,10 +39,7 @@ "xpack.logstash": ["plugins/logstash"], "xpack.main": "legacy/plugins/xpack_main", "xpack.maps": ["plugins/maps"], - "xpack.aiops": [ - "packages/ml/aiops_components", - "plugins/aiops" - ], + "xpack.aiops": ["packages/ml/aiops_components", "plugins/aiops"], "xpack.ml": ["plugins/ml"], "xpack.monitoring": ["plugins/monitoring"], "xpack.osquery": ["plugins/osquery"], diff --git a/x-pack/examples/triggers_actions_ui_example/public/plugin.tsx b/x-pack/examples/triggers_actions_ui_example/public/plugin.tsx index 57c752f140159..20683fb935126 100644 --- a/x-pack/examples/triggers_actions_ui_example/public/plugin.tsx +++ b/x-pack/examples/triggers_actions_ui_example/public/plugin.tsx @@ -142,7 +142,12 @@ export class TriggersActionsUiExamplePlugin useInternalFlyout, getRenderCellValue: () => (props: any) => { const value = props.data.find((d: any) => d.field === props.columnId)?.value ?? []; - return <>{value.length ? value.join() : '--'}; + + if (Array.isArray(value)) { + return <>{value.length ? value.join() : '--'}; + } + + return <>{value}; }, sort, }; diff --git a/x-pack/package.json b/x-pack/package.json index 2433a70c26123..fe6050dd8f95d 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -1,6 +1,6 @@ { "name": "x-pack", - "version": "8.5.0", + "version": "8.6.0", "author": "Elastic", "private": true, "license": "Elastic-License", diff --git a/x-pack/packages/ml/agg_utils/index.ts b/x-pack/packages/ml/agg_utils/index.ts index ac51405d0b8eb..cc7a426f94050 100644 --- a/x-pack/packages/ml/agg_utils/index.ts +++ b/x-pack/packages/ml/agg_utils/index.ts @@ -16,6 +16,7 @@ export type { AggCardinality, ChangePoint, ChangePointGroup, + ChangePointGroupHistogram, ChangePointHistogram, ChangePointHistogramItem, HistogramField, diff --git a/x-pack/packages/ml/agg_utils/src/types.ts b/x-pack/packages/ml/agg_utils/src/types.ts index 0128d5641bf24..a2c0d9f9a1569 100644 --- a/x-pack/packages/ml/agg_utils/src/types.ts +++ b/x-pack/packages/ml/agg_utils/src/types.ts @@ -87,6 +87,14 @@ export interface ChangePointHistogram extends FieldValuePair { histogram: ChangePointHistogramItem[]; } +/** + * Change point histogram data for a group of field/value pairs. + */ +export interface ChangePointGroupHistogram { + id: string; + histogram: ChangePointHistogramItem[]; +} + interface ChangePointGroupItem extends FieldValuePair { duplicate?: boolean; } @@ -95,6 +103,9 @@ interface ChangePointGroupItem extends FieldValuePair { * Tree leaves */ export interface ChangePointGroup { + id: string; group: ChangePointGroupItem[]; docCount: number; + pValue: number | null; + histogram?: ChangePointHistogramItem[]; } diff --git a/x-pack/test/performance/es_archives/ecommerce_sample_data/data.json.gz b/x-pack/performance/es_archives/ecommerce_sample_data/data.json.gz similarity index 100% rename from x-pack/test/performance/es_archives/ecommerce_sample_data/data.json.gz rename to x-pack/performance/es_archives/ecommerce_sample_data/data.json.gz diff --git a/x-pack/test/performance/es_archives/ecommerce_sample_data/mappings.json b/x-pack/performance/es_archives/ecommerce_sample_data/mappings.json similarity index 100% rename from x-pack/test/performance/es_archives/ecommerce_sample_data/mappings.json rename to x-pack/performance/es_archives/ecommerce_sample_data/mappings.json diff --git a/x-pack/test/performance/es_archives/reporting_dashboard/data.json.gz b/x-pack/performance/es_archives/reporting_dashboard/data.json.gz similarity index 100% rename from x-pack/test/performance/es_archives/reporting_dashboard/data.json.gz rename to x-pack/performance/es_archives/reporting_dashboard/data.json.gz diff --git a/x-pack/test/performance/es_archives/reporting_dashboard/mappings.json b/x-pack/performance/es_archives/reporting_dashboard/mappings.json similarity index 100% rename from x-pack/test/performance/es_archives/reporting_dashboard/mappings.json rename to x-pack/performance/es_archives/reporting_dashboard/mappings.json diff --git a/x-pack/performance/journeys/data_stress_test_lens.ts b/x-pack/performance/journeys/data_stress_test_lens.ts new file mode 100644 index 0000000000000..6d65dc5e4a0d2 --- /dev/null +++ b/x-pack/performance/journeys/data_stress_test_lens.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 { Journey } from '@kbn/journeys'; +import { waitForVisualizations } from '../utils'; + +export const journey = new Journey({ + kbnArchives: ['test/functional/fixtures/kbn_archiver/stress_test'], + esArchives: ['test/functional/fixtures/es_archiver/stress_test'], +}).step('Go to dashboard', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/dashboards#/view/92b143a0-2e9c-11ed-b1b6-a504560b392c`)); + + await waitForVisualizations(page, 1); +}); diff --git a/x-pack/performance/journeys/ecommerce_dashboard.ts b/x-pack/performance/journeys/ecommerce_dashboard.ts new file mode 100644 index 0000000000000..89f05902f4153 --- /dev/null +++ b/x-pack/performance/journeys/ecommerce_dashboard.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 { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; + +import { ToastsService } from '../services/toasts'; +import { waitForVisualizations } from '../utils'; + +export const journey = new Journey({ + extendContext: ({ page, log }) => ({ + toasts: new ToastsService(log, page), + }), +}) + .step('Go to Sample Data Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/home#/tutorial_directory/sampleData`)); + + await page.waitForSelector(subj('showSampleDataButton')); + }) + + .step('Open Sample Data pane', async ({ page }) => { + // open the "other sample data sets" section + await page.click(subj('showSampleDataButton')); + // wait for the logs card to be visible + await page.waitForSelector(subj('sampleDataSetCardecommerce')); + }) + + .step('Remove Ecommerce Sample Data if installed', async ({ page, log, toasts }) => { + if (!(await page.$(subj('removeSampleDataSetecommerce')))) { + log.info('Ecommerce data does not need to be removed'); + return; + } + + // click the "remove" button + await page.click(subj('removeSampleDataSetecommerce')); + // wait for the toast acknowledging uninstallation + await toasts.waitForAndClear('uninstalled'); + }) + + .step('Install Ecommerce Sample Data', async ({ page, toasts }) => { + // click the "add data" button + await page.click(subj('addSampleDataSetecommerce')); + // wait for the toast acknowledging installation + await toasts.waitForAndClear('installed'); + }) + + .step('Go to Ecommerce Dashboard', async ({ page }) => { + await page.click(subj('launchSampleDataSetecommerce')); + await page.click(subj('viewSampleDataSetecommerce-dashboard')); + + await waitForVisualizations(page, 13); + }); diff --git a/x-pack/performance/journeys/flight_dashboard.ts b/x-pack/performance/journeys/flight_dashboard.ts new file mode 100644 index 0000000000000..ac6e589d391a5 --- /dev/null +++ b/x-pack/performance/journeys/flight_dashboard.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 { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; + +import { ToastsService } from '../services/toasts'; +import { waitForVisualizations } from '../utils'; + +export const journey = new Journey({ + extendContext: ({ page, log }) => ({ + toasts: new ToastsService(log, page), + }), +}) + .step('Go to Sample Data Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/home#/tutorial_directory/sampleData`)); + + await page.waitForSelector(subj('showSampleDataButton')); + }) + + .step('Open Sample Data pane', async ({ page }) => { + // open the "other sample data sets" section + await page.click(subj('showSampleDataButton')); + // wait for the logs card to be visible + await page.waitForSelector(subj('sampleDataSetCardflights')); + }) + + .step('Remove Flights Sample Data if installed', async ({ page, log, toasts }) => { + if (!(await page.$(subj('removeSampleDataSetflights')))) { + log.info('Flights data does not need to be removed'); + return; + } + + // click the "remove" button + await page.click(subj('removeSampleDataSetflights')); + // wait for the toast acknowledging uninstallation + await toasts.waitForAndClear('uninstalled'); + }) + + .step('Install Flights Sample Data', async ({ page, toasts }) => { + // click the "add data" button + await page.click(subj('addSampleDataSetflights')); + // wait for the toast acknowledging installation + await toasts.waitForAndClear('installed'); + }) + + .step('Go to Flights Dashboard', async ({ page }) => { + await page.click(subj('launchSampleDataSetflights')); + await page.click(subj('viewSampleDataSetflights-dashboard')); + + await waitForVisualizations(page, 15); + }) + + .step('Go to Airport Connections Visualizations Edit', async ({ page }) => { + await page.click(subj('dashboardEditMode')); + + const flightsPanelHeadingSelector = `embeddablePanelHeading-[Flights]AirportConnections(HoverOverAirport)`; + const panelToggleMenuIconSelector = `embeddablePanelToggleMenuIcon`; + await page.click(subj(`${flightsPanelHeadingSelector} > ${panelToggleMenuIconSelector}`)); + + await page.click(subj('embeddablePanelAction-editPanel')); + + await waitForVisualizations(page, 1); + }); diff --git a/x-pack/performance/journeys/login.ts b/x-pack/performance/journeys/login.ts new file mode 100644 index 0000000000000..7c5808be83607 --- /dev/null +++ b/x-pack/performance/journeys/login.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; + +export const journey = new Journey({ + skipAutoLogin: true, + scalabilitySetup: { + warmup: [ + { + action: 'constantConcurrentUsers', + userCount: 10, + duration: '30s', + }, + { + action: 'rampConcurrentUsers', + minUsersCount: 10, + maxUsersCount: 50, + duration: '2m', + }, + ], + test: [ + { + action: 'constantConcurrentUsers', + userCount: 50, + duration: '5m', + }, + ], + maxDuration: '10m', + }, +}).step('Login', async ({ page, kbnUrl, inputDelays }) => { + await page.goto(kbnUrl.get()); + + await page.type(subj('loginUsername'), 'elastic', { delay: inputDelays.TYPING }); + await page.type(subj('loginPassword'), 'changeme', { delay: inputDelays.TYPING }); + await page.click(subj('loginSubmit'), { delay: inputDelays.MOUSE_CLICK }); + + await page.waitForSelector('#headerUserMenu'); +}); diff --git a/x-pack/performance/journeys/many_fields_discover.ts b/x-pack/performance/journeys/many_fields_discover.ts new file mode 100644 index 0000000000000..41ec0373c700c --- /dev/null +++ b/x-pack/performance/journeys/many_fields_discover.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 { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; + +export const journey = new Journey({ + // FAILING: https://github.com/elastic/kibana/issues/130287 + skipped: true, + kbnArchives: ['test/functional/fixtures/kbn_archiver/many_fields_data_view'], + esArchives: ['test/functional/fixtures/es_archiver/many_fields'], +}) + .step('Go to Discover Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/discover`)); + await page.waitForSelector(subj('discoverDocTable')); + }) + .step('Expand the first document', async ({ page }) => { + const expandButtons = page.locator(subj('docTableExpandToggleColumn')); + await expandButtons.first().click(); + await page.locator('text="Expanded document"'); + }); diff --git a/x-pack/performance/journeys/promotion_tracking_dashboard.ts b/x-pack/performance/journeys/promotion_tracking_dashboard.ts new file mode 100644 index 0000000000000..e6bd67a2819c5 --- /dev/null +++ b/x-pack/performance/journeys/promotion_tracking_dashboard.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; +import { waitForVisualizations } from '../utils'; + +export const journey = new Journey({ + kbnArchives: ['x-pack/performance/kbn_archives/promotion_tracking_dashboard'], + esArchives: ['x-pack/performance/es_archives/ecommerce_sample_data'], + scalabilitySetup: { + warmup: [ + { + action: 'constantConcurrentUsers', + userCount: 10, + duration: '30s', + }, + { + action: 'rampConcurrentUsers', + minUsersCount: 10, + maxUsersCount: 50, + duration: '2m', + }, + ], + test: [ + { + action: 'constantConcurrentUsers', + userCount: 50, + duration: '5m', + }, + ], + maxDuration: '10m', + }, +}) + .step('Go to Dashboards Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/dashboards`)); + await page.waitForSelector('#dashboardListingHeading'); + }) + + .step('Go to Promotion Tracking Dashboard', async ({ page }) => { + await page.click(subj('dashboardListingTitleLink-Promotion-Dashboard')); + }) + + .step('Change time range', async ({ page }) => { + await page.click(subj('superDatePickerToggleQuickMenuButton')); + await page.click(subj('superDatePickerCommonlyUsed_Last_30 days')); + }) + + .step('Wait for visualization animations to finish', async ({ page }) => { + await waitForVisualizations(page, 1); + }); diff --git a/x-pack/performance/journeys/web_logs_dashboard.ts b/x-pack/performance/journeys/web_logs_dashboard.ts new file mode 100644 index 0000000000000..64ea47d412e0e --- /dev/null +++ b/x-pack/performance/journeys/web_logs_dashboard.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 { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; + +import { ToastsService } from '../services/toasts'; +import { waitForVisualizations } from '../utils'; + +export const journey = new Journey({ + extendContext: ({ page, log }) => ({ + toasts: new ToastsService(log, page), + }), +}) + .step('Go to Sample Data Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/home#/tutorial_directory/sampleData`)); + + await page.waitForSelector(subj('showSampleDataButton')); + }) + + .step('Open Sample Data pane', async ({ page }) => { + // open the "other sample data sets" section + await page.click(subj('showSampleDataButton')); + // wait for the logs card to be visible + await page.waitForSelector(subj('sampleDataSetCardlogs')); + }) + + .step('Remove Sample Data Logs if installed', async ({ page, log, toasts }) => { + if (!(await page.$(subj('removeSampleDataSetlogs')))) { + log.info('Logs data does not need to be removed'); + return; + } + + // click the "remove" button + await page.click(subj('removeSampleDataSetlogs')); + // wait for the toast acknowledging uninstallation + await toasts.waitForAndClear('uninstalled'); + }) + + .step('Install Logs Sample Data', async ({ page, toasts }) => { + // click the "add data" button + await page.click(subj('addSampleDataSetlogs')); + // wait for the toast acknowledging installation + await toasts.waitForAndClear('installed'); + }) + + .step('Go to Web Logs Dashboard', async ({ page }) => { + await page.click(subj('launchSampleDataSetlogs')); + await page.click(subj('viewSampleDataSetlogs-dashboard')); + + await waitForVisualizations(page, 12); + }); diff --git a/x-pack/test/performance/kbn_archives/promotion_tracking_dashboard.json b/x-pack/performance/kbn_archives/promotion_tracking_dashboard.json similarity index 100% rename from x-pack/test/performance/kbn_archives/promotion_tracking_dashboard.json rename to x-pack/performance/kbn_archives/promotion_tracking_dashboard.json diff --git a/x-pack/test/performance/kbn_archives/reporting_dashboard.json b/x-pack/performance/kbn_archives/reporting_dashboard.json similarity index 100% rename from x-pack/test/performance/kbn_archives/reporting_dashboard.json rename to x-pack/performance/kbn_archives/reporting_dashboard.json diff --git a/x-pack/performance/services/toasts.ts b/x-pack/performance/services/toasts.ts new file mode 100644 index 0000000000000..b3859ddb92ec4 --- /dev/null +++ b/x-pack/performance/services/toasts.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 { ToolingLog } from '@kbn/tooling-log'; +import { subj } from '@kbn/test-subj-selector'; +import { Page } from 'playwright'; + +export class ToastsService { + constructor(private readonly log: ToolingLog, private readonly page: Page) {} + + /** + * Wait for a toast with some bit of text matching the provided `textSnipped`, then clear + * it and resolve the promise. + */ + async waitForAndClear(textSnippet: string) { + const txt = JSON.stringify(textSnippet); + this.log.info(`waiting for toast that has the text ${txt}`); + const toastSel = `.euiToast:has-text(${txt})`; + + const toast = this.page.locator(toastSel); + await toast.waitFor(); + + this.log.info('toast found, closing'); + + const close = toast.locator(subj('toastCloseButton')); + await close.click(); + + await toast.waitFor({ state: 'hidden' }); + } +} diff --git a/x-pack/performance/tsconfig.json b/x-pack/performance/tsconfig.json new file mode 100644 index 0000000000000..caff6d53f4476 --- /dev/null +++ b/x-pack/performance/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "types": ["node", "mocha"] + }, + "include": ["**/*.ts"], +} diff --git a/x-pack/test/performance/utils.ts b/x-pack/performance/utils.ts similarity index 67% rename from x-pack/test/performance/utils.ts rename to x-pack/performance/utils.ts index 1e9e754088418..c0a7ba95f7ee1 100644 --- a/x-pack/test/performance/utils.ts +++ b/x-pack/performance/utils.ts @@ -7,14 +7,8 @@ import { Page } from 'playwright'; -export function serializeApmGlobalLabels(obj: any) { - return Object.entries(obj) - .filter(([, v]) => !!v) - .reduce((acc, [k, v]) => (acc ? `${acc},${k}=${v}` : `${k}=${v}`), ''); -} - -export function waitForVisualizations(page: Page, visCount: number) { - return page.waitForFunction(function renderCompleted(cnt) { +export async function waitForVisualizations(page: Page, visCount: number) { + return await page.waitForFunction(function renderCompleted(cnt) { const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); const visualizationElementsLoaded = visualizations.length === cnt; const visualizationAnimationsFinished = visualizations.every( diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts index b5606543b04e5..7216f627ea783 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts @@ -721,6 +721,36 @@ Object { }, }, }, + count_connector_types_by_action_run_outcome_per_day: { + actionSavedObjects: { + connector_types: { + buckets: [ + { + key: '.slack', + outcome: { + count: { + buckets: [ + { key: 'success', doc_count: 12 }, + { key: 'failure', doc_count: 1 }, + ], + }, + }, + }, + { + key: '.email', + outcome: { + count: { + buckets: [ + { key: 'success', doc_count: 13 }, + { key: 'failure', doc_count: 2 }, + ], + }, + }, + }, + ], + }, + }, + }, }, } ); @@ -754,6 +784,16 @@ Object { __slack: 7, }, countTotal: 120, + countRunOutcomeByConnectorType: { + __email: { + failure: 2, + success: 13, + }, + __slack: { + failure: 1, + success: 12, + }, + }, hasErrors: false, }); }); @@ -775,6 +815,7 @@ Object { "countByType": Object {}, "countFailed": 0, "countFailedByType": Object {}, + "countRunOutcomeByConnectorType": Object {}, "countTotal": 0, "errorMessage": "oh no", "hasErrors": true, diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index 1e2320e2b1a5d..15d2d6f4d12e5 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -7,6 +7,11 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { AggregationsTermsAggregateBase } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + AvgActionRunOutcomeByConnectorTypeBucket, + parseActionRunOutcomeByConnectorTypesBucket, +} from './lib/parse_connector_type_bucket'; import { AlertHistoryEsIndexConnectorId } from '../../common'; import { ActionResult, PreConfiguredAction } from '../types'; @@ -395,7 +400,7 @@ export async function getInUseTotalCount( } } -function replaceFirstAndLastDotSymbols(strToReplace: string) { +export function replaceFirstAndLastDotSymbols(strToReplace: string) { const hasFirstSymbolDot = strToReplace.startsWith('.'); const appliedString = hasFirstSymbolDot ? strToReplace.replace('.', '__') : strToReplace; const hasLastSymbolDot = strToReplace.endsWith('.'); @@ -415,6 +420,7 @@ export async function getExecutionsPerDayCount( countFailedByType: Record; avgExecutionTime: number; avgExecutionTimeByType: Record; + countRunOutcomeByConnectorType: Record; }> { const scriptedMetric = { scripted_metric: { @@ -536,6 +542,35 @@ export async function getExecutionsPerDayCount( }, }, }, + count_connector_types_by_action_run_outcome_per_day: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + actionSavedObjects: { + filter: { term: { 'kibana.saved_objects.type': 'action' } }, + aggs: { + connector_types: { + terms: { + field: 'kibana.saved_objects.type_id', + }, + aggs: { + outcome: { + reverse_nested: {}, + aggs: { + count: { + terms: { + field: 'event.outcome', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, }, }, }); @@ -564,6 +599,14 @@ export async function getExecutionsPerDayCount( {} ); + const aggsCountConnectorTypeByActionRun = actionResults.aggregations as { + count_connector_types_by_action_run_outcome_per_day: { + actionSavedObjects: { + connector_types: AggregationsTermsAggregateBase; + }; + }; + }; + return { hasErrors: false, countTotal: aggsExecutions.total, @@ -586,6 +629,10 @@ export async function getExecutionsPerDayCount( ), avgExecutionTime: aggsAvgExecutionTime, avgExecutionTimeByType, + countRunOutcomeByConnectorType: parseActionRunOutcomeByConnectorTypesBucket( + aggsCountConnectorTypeByActionRun.count_connector_types_by_action_run_outcome_per_day + .actionSavedObjects.connector_types.buckets + ), }; } catch (err) { const errorMessage = err && err.message ? err.message : err.toString(); @@ -601,6 +648,7 @@ export async function getExecutionsPerDayCount( countFailedByType: {}, avgExecutionTime: 0, avgExecutionTimeByType: {}, + countRunOutcomeByConnectorType: {}, }; } } diff --git a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts index f6e02b2e0fec9..f49525a4eec05 100644 --- a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts +++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts @@ -47,6 +47,13 @@ export function createActionsUsageCollector( count_actions_executions_failed_by_type_per_day: byTypeSchema, avg_execution_time_per_day: { type: 'long' }, avg_execution_time_by_type_per_day: byTypeSchema, + count_connector_types_by_action_run_outcome_per_day: { + DYNAMIC_KEY: { + success: { type: 'long' }, + failure: { type: 'long' }, + unknown: { type: 'long' }, + }, + }, }, fetch: async () => { try { @@ -77,6 +84,7 @@ export function createActionsUsageCollector( count_actions_executions_failed_by_type_per_day: {}, avg_execution_time_per_day: 0, avg_execution_time_by_type_per_day: {}, + count_connector_types_by_action_run_outcome_per_day: {}, }; } }, diff --git a/x-pack/plugins/actions/server/usage/lib/parse_connector_type_bucket.test.ts b/x-pack/plugins/actions/server/usage/lib/parse_connector_type_bucket.test.ts new file mode 100644 index 0000000000000..1241cddfeb55e --- /dev/null +++ b/x-pack/plugins/actions/server/usage/lib/parse_connector_type_bucket.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { parseActionRunOutcomeByConnectorTypesBucket } from './parse_connector_type_bucket'; + +describe('parseActionRunOutcomeByConnectorTypesBucket', () => { + test('should correctly parse connector type bucket results', () => { + expect( + parseActionRunOutcomeByConnectorTypesBucket([ + { + key: '.server-log', + doc_count: 78, + outcome: { + count: { + buckets: [ + { key: 'success', doc_count: 2 }, + { key: 'failure', doc_count: 1 }, + ], + }, + }, + }, + { + key: '.index', + doc_count: 42, + outcome: { + count: { + buckets: [ + { key: 'success', doc_count: 3 }, + { key: 'failure', doc_count: 4 }, + ], + }, + }, + }, + ]) + ).toEqual({ + __index: { + failure: 4, + success: 3, + }, + '__server-log': { + failure: 1, + success: 2, + }, + }); + }); + + test('should handle missing values', () => { + expect( + parseActionRunOutcomeByConnectorTypesBucket([ + { + key: '.server-log', + doc_count: 78, + outcome: { + count: { + // @ts-expect-error + buckets: [{ key: 'success', doc_count: 2 }, { key: 'failure' }], + }, + }, + }, + { + key: '.index', + outcome: { + // @ts-expect-error + count: {}, + }, + }, + ]) + ).toEqual({ + '__server-log': { + failure: 0, + success: 2, + }, + __index: {}, + }); + }); + + test('should handle empty input', () => { + expect(parseActionRunOutcomeByConnectorTypesBucket([])).toEqual({}); + }); + // + test('should handle undefined input', () => { + expect(parseActionRunOutcomeByConnectorTypesBucket(undefined)).toEqual({}); + }); +}); diff --git a/x-pack/plugins/actions/server/usage/lib/parse_connector_type_bucket.ts b/x-pack/plugins/actions/server/usage/lib/parse_connector_type_bucket.ts new file mode 100644 index 0000000000000..96e1610c635d8 --- /dev/null +++ b/x-pack/plugins/actions/server/usage/lib/parse_connector_type_bucket.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 { AggregationsBuckets } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { replaceFirstAndLastDotSymbols } from '../actions_telemetry'; + +export interface AvgActionRunOutcomeByConnectorTypeBucket { + key: string; + doc_count: number; // Not used for duration telemetry but can be helpful later. + outcome: { count: { buckets: Array<{ key: string; doc_count: number }> } }; +} + +export function parseActionRunOutcomeByConnectorTypesBucket( + connectorTypeBuckets: AggregationsBuckets = [] +) { + const connectorTypes = connectorTypeBuckets as AvgActionRunOutcomeByConnectorTypeBucket[]; + return connectorTypes.reduce((acc, connectorType) => { + const outcomes = connectorType.outcome?.count?.buckets ?? []; + return { + ...acc, + [replaceFirstAndLastDotSymbols(connectorType.key)]: outcomes.reduce((accBucket, bucket) => { + return { ...accBucket, [replaceFirstAndLastDotSymbols(bucket.key)]: bucket.doc_count || 0 }; + }, {}), + }; + }, {}); +} diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index 15f70529d3852..e8861b30ae8b4 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -130,6 +130,8 @@ export function telemetryTaskRunner( totalExecutionsPerDay.countFailedByType, avg_execution_time_per_day: totalExecutionsPerDay.avgExecutionTime, avg_execution_time_by_type_per_day: totalExecutionsPerDay.avgExecutionTimeByType, + count_connector_types_by_action_run_outcome_per_day: + totalExecutionsPerDay.countRunOutcomeByConnectorType, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/actions/server/usage/types.ts b/x-pack/plugins/actions/server/usage/types.ts index b58ac9c096f63..62ee11af72f8f 100644 --- a/x-pack/plugins/actions/server/usage/types.ts +++ b/x-pack/plugins/actions/server/usage/types.ts @@ -22,6 +22,7 @@ export interface ActionsUsage { count_actions_executions_by_type_per_day: Record; count_actions_executions_failed_per_day: number; count_actions_executions_failed_by_type_per_day: Record; + count_connector_types_by_action_run_outcome_per_day: Record>; avg_execution_time_per_day: number; avg_execution_time_by_type_per_day: Record; } diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts index 938c765d8e0d2..e050946a489be 100644 --- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts +++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts @@ -5,12 +5,18 @@ * 2.0. */ -import type { ChangePoint, ChangePointHistogram, ChangePointGroup } from '@kbn/ml-agg-utils'; +import type { + ChangePoint, + ChangePointHistogram, + ChangePointGroup, + ChangePointGroupHistogram, +} from '@kbn/ml-agg-utils'; export const API_ACTION_NAME = { ADD_CHANGE_POINTS: 'add_change_points', ADD_CHANGE_POINTS_HISTOGRAM: 'add_change_points_histogram', ADD_CHANGE_POINTS_GROUP: 'add_change_point_group', + ADD_CHANGE_POINTS_GROUP_HISTOGRAM: 'add_change_point_group_histogram', ADD_ERROR: 'add_error', RESET: 'reset', UPDATE_LOADING_STATE: 'update_loading_state', @@ -57,6 +63,20 @@ export function addChangePointsGroupAction(payload: ApiActionAddChangePointsGrou }; } +interface ApiActionAddChangePointsGroupHistogram { + type: typeof API_ACTION_NAME.ADD_CHANGE_POINTS_GROUP_HISTOGRAM; + payload: ChangePointGroupHistogram[]; +} + +export function addChangePointsGroupHistogramAction( + payload: ApiActionAddChangePointsGroupHistogram['payload'] +): ApiActionAddChangePointsGroupHistogram { + return { + type: API_ACTION_NAME.ADD_CHANGE_POINTS_GROUP_HISTOGRAM, + payload, + }; +} + interface ApiActionAddError { type: typeof API_ACTION_NAME.ADD_ERROR; payload: string; @@ -99,6 +119,7 @@ export type AiopsExplainLogRateSpikesApiAction = | ApiActionAddChangePoints | ApiActionAddChangePointsGroup | ApiActionAddChangePointsHistogram + | ApiActionAddChangePointsGroupHistogram | ApiActionAddError | ApiActionReset | ApiActionUpdateLoadingState; diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts index dbc6be43766c6..5628b509980ad 100644 --- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts +++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts @@ -8,6 +8,7 @@ export { addChangePointsAction, addChangePointsGroupAction, + addChangePointsGroupHistogramAction, addChangePointsHistogramAction, addErrorAction, resetAction, diff --git a/x-pack/plugins/aiops/common/api/stream_reducer.ts b/x-pack/plugins/aiops/common/api/stream_reducer.ts index ff275d1414e91..690db961f5121 100644 --- a/x-pack/plugins/aiops/common/api/stream_reducer.ts +++ b/x-pack/plugins/aiops/common/api/stream_reducer.ts @@ -51,6 +51,15 @@ export function streamReducer( return { ...state, changePoints }; case API_ACTION_NAME.ADD_CHANGE_POINTS_GROUP: return { ...state, changePointsGroups: action.payload }; + case API_ACTION_NAME.ADD_CHANGE_POINTS_GROUP_HISTOGRAM: + const changePointsGroups = state.changePointsGroups.map((cpg) => { + const cpHistogram = action.payload.find((h) => h.id === cpg.id); + if (cpHistogram) { + cpg.histogram = cpHistogram.histogram; + } + return cpg; + }); + return { ...state, changePointsGroups }; case API_ACTION_NAME.ADD_ERROR: return { ...state, errors: [...state.errors, action.payload] }; case API_ACTION_NAME.RESET: diff --git a/x-pack/plugins/aiops/public/application/utils/query_utils.ts b/x-pack/plugins/aiops/public/application/utils/query_utils.ts index 85ad3d236a98e..1779b434df508 100644 --- a/x-pack/plugins/aiops/public/application/utils/query_utils.ts +++ b/x-pack/plugins/aiops/public/application/utils/query_utils.ts @@ -16,6 +16,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Query } from '@kbn/es-query'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { ChangePoint } from '@kbn/ml-agg-utils'; +import type { GroupTableItem } from '../../components/spike_analysis_table/spike_analysis_table_groups'; /* * Contains utility functions for building and processing queries. @@ -29,7 +30,8 @@ export function buildBaseFilterCriteria( latestMs?: number, query?: Query['query'], selectedChangePoint?: ChangePoint, - includeSelectedChangePoint = true + includeSelectedChangePoint = true, + selectedGroup?: GroupTableItem | null ): estypes.QueryDslQueryContainer[] { const filterCriteria = []; if (timeFieldName && earliestMs && latestMs) { @@ -48,10 +50,24 @@ export function buildBaseFilterCriteria( filterCriteria.push(query); } - if (selectedChangePoint && includeSelectedChangePoint) { - filterCriteria.push({ - term: { [selectedChangePoint.fieldName]: selectedChangePoint.fieldValue }, - }); + const groupFilter = []; + if (selectedGroup) { + const allItems = { ...selectedGroup.group, ...selectedGroup.repeatedValues }; + for (const fieldName in allItems) { + if (allItems.hasOwnProperty(fieldName)) { + groupFilter.push({ term: { [fieldName]: allItems[fieldName] } }); + } + } + } + + if (includeSelectedChangePoint) { + if (selectedChangePoint) { + filterCriteria.push({ + term: { [selectedChangePoint.fieldName]: selectedChangePoint.fieldValue }, + }); + } else if (selectedGroup) { + filterCriteria.push(...groupFilter); + } } else if (selectedChangePoint && !includeSelectedChangePoint) { filterCriteria.push({ bool: { @@ -62,6 +78,18 @@ export function buildBaseFilterCriteria( ], }, }); + } else if (selectedGroup && !includeSelectedChangePoint) { + filterCriteria.push({ + bool: { + must_not: [ + { + bool: { + filter: [...groupFilter], + }, + }, + ], + }, + }); } return filterCriteria; 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 04ec62fbe6381..1f179be0ba975 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 @@ -6,7 +6,6 @@ */ import React, { useEffect, useState, FC, useMemo } from 'react'; -import { min, max } from 'd3-array'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; @@ -51,8 +50,8 @@ export const DocumentCountContent: FC = ({ }, [windowParameters]); const bucketTimestamps = Object.keys(documentCountStats?.buckets ?? {}).map((time) => +time); - const timeRangeEarliest = min(bucketTimestamps); - const timeRangeLatest = max(bucketTimestamps); + const timeRangeEarliest = Math.min(...bucketTimestamps); + const timeRangeLatest = Math.max(...bucketTimestamps); const chartPointsSplitLabel = useMemo( () => `${changePoint?.fieldName}:${changePoint?.fieldValue}`, [changePoint] diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx index 98d6053a8b6f6..a0fb825ee4064 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx @@ -32,6 +32,7 @@ import type { ApiExplainLogRateSpikes } from '../../../common/api'; import { SpikeAnalysisGroupsTable } from '../spike_analysis_table'; import { SpikeAnalysisTable } from '../spike_analysis_table'; +import { GroupTableItem } from '../spike_analysis_table/spike_analysis_table_groups'; const groupResultsMessage = i18n.translate( 'xpack.aiops.spikeAnalysisTable.groupedSwitchLabel.groupResults', @@ -56,6 +57,7 @@ interface ExplainLogRateSpikesAnalysisProps { onPinnedChangePoint?: (changePoint: ChangePoint | null) => void; onSelectedChangePoint?: (changePoint: ChangePoint | null) => void; selectedChangePoint?: ChangePoint; + onSelectedGroup?: (group: GroupTableItem | null) => void; } export const ExplainLogRateSpikesAnalysis: FC = ({ @@ -67,6 +69,7 @@ export const ExplainLogRateSpikesAnalysis: FC onPinnedChangePoint, onSelectedChangePoint, selectedChangePoint, + onSelectedGroup, }) => { const { http } = useAiopsAppContext(); const basePath = http.basePath.get() ?? ''; @@ -74,7 +77,7 @@ export const ExplainLogRateSpikesAnalysis: FC const [currentAnalysisWindowParameters, setCurrentAnalysisWindowParameters] = useState< WindowParameters | undefined >(); - const [groupResults, setGroupResults] = useState(true); + const [groupResults, setGroupResults] = useState(false); const onSwitchToggle = (e: { target: { checked: React.SetStateAction } }) => { setGroupResults(e.target.checked); @@ -106,6 +109,9 @@ export const ExplainLogRateSpikesAnalysis: FC // Start handler clears possibly hovered or pinned // change points on analysis refresh. function startHandler() { + // Reset grouping to false when restarting the analysis. + setGroupResults(false); + if (onPinnedChangePoint) { onPinnedChangePoint(null); } @@ -124,7 +130,7 @@ export const ExplainLogRateSpikesAnalysis: FC }, []); const groupTableItems = useMemo(() => { - const tableItems = data.changePointsGroups.map(({ group, docCount }, index) => { + const tableItems = data.changePointsGroups.map(({ id, group, docCount, histogram, pValue }) => { const sortedGroup = group.sort((a, b) => a.fieldName > b.fieldName ? 1 : b.fieldName > a.fieldName ? -1 : 0 ); @@ -141,10 +147,12 @@ export const ExplainLogRateSpikesAnalysis: FC }); return { - id: index, + id, docCount, + pValue, group: dedupedGroup, repeatedValues, + histogram, }; }); @@ -162,8 +170,7 @@ export const ExplainLogRateSpikesAnalysis: FC const groupItemCount = groupTableItems.reduce((p, c) => { return p + Object.keys(c.group).length; }, 0); - const foundGroups = - groupTableItems.length === 0 || (groupTableItems.length > 0 && groupItemCount > 0); + const foundGroups = groupTableItems.length > 0 && groupItemCount > 0; return (
@@ -178,6 +185,7 @@ export const ExplainLogRateSpikesAnalysis: FC {showSpikeAnalysisTable && foundGroups && ( onPinnedChangePoint={onPinnedChangePoint} onSelectedChangePoint={onSelectedChangePoint} selectedChangePoint={selectedChangePoint} + onSelectedGroup={onSelectedGroup} dataViewId={dataView.id} /> ) : null} diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx index bdc91175c2913..feff4f2e8211f 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx @@ -37,6 +37,7 @@ import { SearchPanel } from '../search_panel'; import { restorableDefaults } from './explain_log_rate_spikes_app_state'; import { ExplainLogRateSpikesAnalysis } from './explain_log_rate_spikes_analysis'; +import type { GroupTableItem } from '../spike_analysis_table/spike_analysis_table_groups'; // TODO port to `@emotion/react` once `useEuiBreakpoint` is available https://github.com/elastic/eui/pull/6057 import './explain_log_rate_spikes_page.scss'; @@ -94,6 +95,7 @@ export const ExplainLogRateSpikesPage: FC = ({ const [pinnedChangePoint, setPinnedChangePoint] = useState(null); const [selectedChangePoint, setSelectedChangePoint] = useState(null); + const [selectedGroup, setSelectedGroup] = useState(null); // If a row is pinned, still overrule with a potentially hovered row. const currentSelectedChangePoint = useMemo(() => { @@ -116,7 +118,9 @@ export const ExplainLogRateSpikesPage: FC = ({ { currentDataView: dataView, currentSavedSearch }, aiopsListState, setGlobalState, - currentSelectedChangePoint + currentSelectedChangePoint, + undefined, + selectedGroup ); const { totalCount, documentCountStats, documentCountStatsCompare } = documentStats; @@ -167,6 +171,7 @@ export const ExplainLogRateSpikesPage: FC = ({ setWindowParameters(undefined); setPinnedChangePoint(null); setSelectedChangePoint(null); + setSelectedGroup(null); } return ( @@ -225,7 +230,9 @@ export const ExplainLogRateSpikesPage: FC = ({ clearSelectionHandler={clearSelection} documentCountStats={documentCountStats} documentCountStatsSplit={ - currentSelectedChangePoint ? documentCountStatsCompare : undefined + currentSelectedChangePoint || selectedGroup + ? documentCountStatsCompare + : undefined } totalCount={totalCount} changePoint={currentSelectedChangePoint} @@ -246,6 +253,7 @@ export const ExplainLogRateSpikesPage: FC = ({ onPinnedChangePoint={setPinnedChangePoint} onSelectedChangePoint={setSelectedChangePoint} selectedChangePoint={currentSelectedChangePoint} + onSelectedGroup={setSelectedGroup} /> )} {windowParameters === undefined && ( diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx index 8a65a938d38c9..35f3ba368be9a 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table.tsx @@ -32,6 +32,7 @@ import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transa const NARROW_COLUMN_WIDTH = '120px'; const ACTIONS_COLUMN_WIDTH = '60px'; +const NOT_AVAILABLE = '--'; const PAGINATION_SIZE_OPTIONS = [5, 10, 20, 50]; const DEFAULT_SORT_FIELD = 'pValue'; @@ -129,21 +130,21 @@ export const SpikeAnalysisTable: FC = ({ { 'data-test-subj': 'aiopsSpikeAnalysisTableColumnFieldName', field: 'fieldName', - name: i18n.translate( - 'xpack.aiops.correlations.failedTransactions.correlationsTable.fieldNameLabel', - { defaultMessage: 'Field name' } - ), + name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.fieldNameLabel', { + defaultMessage: 'Field name', + }), sortable: true, + valign: 'top', }, { 'data-test-subj': 'aiopsSpikeAnalysisTableColumnFieldValue', field: 'fieldValue', - name: i18n.translate( - 'xpack.aiops.correlations.failedTransactions.correlationsTable.fieldValueLabel', - { defaultMessage: 'Field value' } - ), + name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.fieldValueLabel', { + defaultMessage: 'Field value', + }), render: (_, { fieldValue }) => String(fieldValue).slice(0, 50), sortable: true, + valign: 'top', }, { 'data-test-subj': 'aiopsSpikeAnalysisTableColumnLogRate', @@ -153,7 +154,7 @@ export const SpikeAnalysisTable: FC = ({ = ({ > <> @@ -177,6 +178,17 @@ export const SpikeAnalysisTable: FC = ({ /> ), sortable: false, + valign: 'top', + }, + { + 'data-test-subj': 'aiopsSpikeAnalysisTableColumnDocCount', + width: NARROW_COLUMN_WIDTH, + field: 'doc_count', + name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.docCountLabel', { + defaultMessage: 'Doc count', + }), + sortable: true, + valign: 'top', }, { 'data-test-subj': 'aiopsSpikeAnalysisTableColumnPValue', @@ -186,7 +198,7 @@ export const SpikeAnalysisTable: FC = ({ = ({ > <> ), - render: (pValue: number) => pValue.toPrecision(3), + render: (pValue: number | null) => pValue?.toPrecision(3) ?? NOT_AVAILABLE, sortable: true, + valign: 'top', }, { 'data-test-subj': 'aiopsSpikeAnalysisTableColumnImpact', @@ -213,7 +226,7 @@ export const SpikeAnalysisTable: FC = ({ = ({ > <> @@ -229,10 +242,12 @@ export const SpikeAnalysisTable: FC = ({ ), render: (_, { pValue }) => { + if (!pValue) return NOT_AVAILABLE; const label = getFailedTransactionsCorrelationImpactLabel(pValue); return label ? {label.impact} : null; }, sortable: true, + valign: 'top', }, { 'data-test-subj': 'aiOpsSpikeAnalysisTableColumnAction', @@ -258,6 +273,7 @@ export const SpikeAnalysisTable: FC = ({ }, ], width: ACTIONS_COLUMN_WIDTH, + valign: 'top', }, ]; diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_expanded_row.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_expanded_row.tsx deleted file mode 100644 index 367e5f32c695b..0000000000000 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_expanded_row.tsx +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { FC, useCallback, useMemo, useState } from 'react'; -import { sortBy } from 'lodash'; - -import { - EuiBadge, - EuiBasicTable, - EuiBasicTableColumn, - EuiIcon, - EuiTableSortingType, - EuiToolTip, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { escapeKuery } from '@kbn/es-query'; -import type { ChangePoint } from '@kbn/ml-agg-utils'; - -import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils'; -import { useEuiTheme } from '../../hooks/use_eui_theme'; -import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; - -import { MiniHistogram } from '../mini_histogram'; - -import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label'; - -const NARROW_COLUMN_WIDTH = '120px'; -const ACTIONS_COLUMN_WIDTH = '60px'; -const NOT_AVAILABLE = '--'; - -const PAGINATION_SIZE_OPTIONS = [5, 10, 20, 50]; -const DEFAULT_SORT_FIELD = 'pValue'; -const DEFAULT_SORT_DIRECTION = 'asc'; -const viewInDiscoverMessage = i18n.translate( - 'xpack.aiops.spikeAnalysisTable.linksMenu.viewInDiscover', - { - defaultMessage: 'View in Discover', - } -); - -interface SpikeAnalysisTableExpandedRowProps { - changePoints: ChangePoint[]; - dataViewId?: string; - loading: boolean; - onPinnedChangePoint?: (changePoint: ChangePoint | null) => void; - onSelectedChangePoint?: (changePoint: ChangePoint | null) => void; - selectedChangePoint?: ChangePoint; -} - -export const SpikeAnalysisTableExpandedRow: FC = ({ - changePoints, - dataViewId, - loading, - onPinnedChangePoint, - onSelectedChangePoint, - selectedChangePoint, -}) => { - const euiTheme = useEuiTheme(); - - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(10); - const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); - const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION); - - const { application, share, data } = useAiopsAppContext(); - - const discoverLocator = useMemo( - () => share.url.locators.get('DISCOVER_APP_LOCATOR'), - [share.url.locators] - ); - - const discoverUrlError = useMemo(() => { - if (!application.capabilities.discover?.show) { - const discoverNotEnabled = i18n.translate( - 'xpack.aiops.spikeAnalysisTable.discoverNotEnabledErrorMessage', - { - defaultMessage: 'Discover is not enabled', - } - ); - - return discoverNotEnabled; - } - if (!discoverLocator) { - const discoverLocatorMissing = i18n.translate( - 'xpack.aiops.spikeAnalysisTable.discoverLocatorMissingErrorMessage', - { - defaultMessage: 'No locator for Discover detected', - } - ); - - return discoverLocatorMissing; - } - if (!dataViewId) { - const autoGeneratedDiscoverLinkError = i18n.translate( - 'xpack.aiops.spikeAnalysisTable.autoGeneratedDiscoverLinkErrorMessage', - { - defaultMessage: 'Unable to link to Discover; no data view exists for this index', - } - ); - - return autoGeneratedDiscoverLinkError; - } - }, [application.capabilities.discover?.show, dataViewId, discoverLocator]); - - const generateDiscoverUrl = async (changePoint: ChangePoint) => { - if (discoverLocator !== undefined) { - const url = await discoverLocator.getRedirectUrl({ - indexPatternId: dataViewId, - timeRange: data.query.timefilter.timefilter.getTime(), - filters: data.query.filterManager.getFilters(), - query: { - language: SEARCH_QUERY_LANGUAGE.KUERY, - query: `${escapeKuery(changePoint.fieldName)}:${escapeKuery( - String(changePoint.fieldValue) - )}`, - }, - }); - - return url; - } - }; - - const columns: Array> = [ - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnFieldName', - field: 'fieldName', - name: i18n.translate( - 'xpack.aiops.correlations.failedTransactions.correlationsTable.fieldNameLabel', - { defaultMessage: 'Field name' } - ), - sortable: true, - }, - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnFieldValue', - field: 'fieldValue', - name: i18n.translate( - 'xpack.aiops.correlations.failedTransactions.correlationsTable.fieldValueLabel', - { defaultMessage: 'Field value' } - ), - render: (_, { fieldValue }) => String(fieldValue).slice(0, 50), - sortable: true, - }, - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnLogRate', - width: NARROW_COLUMN_WIDTH, - field: 'pValue', - name: ( - - <> - - - - - ), - render: (_, { histogram, fieldName, fieldValue }) => { - if (!histogram) return NOT_AVAILABLE; - return ( - - ); - }, - sortable: false, - }, - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnPValue', - width: NARROW_COLUMN_WIDTH, - field: 'pValue', - name: ( - - <> - - - - - ), - render: (pValue: number) => pValue?.toPrecision(3) ?? NOT_AVAILABLE, - sortable: true, - }, - { - 'data-test-subj': 'aiopsSpikeAnalysisTableColumnImpact', - width: NARROW_COLUMN_WIDTH, - field: 'pValue', - name: ( - - <> - - - - - ), - render: (_, { pValue }) => { - if (!pValue) return NOT_AVAILABLE; - const label = getFailedTransactionsCorrelationImpactLabel(pValue); - return label ? {label.impact} : null; - }, - sortable: true, - }, - { - 'data-test-subj': 'aiOpsSpikeAnalysisTableColumnAction', - name: i18n.translate('xpack.aiops.spikeAnalysisTable.actionsColumnName', { - defaultMessage: 'Actions', - }), - actions: [ - { - name: () => ( - - - - ), - description: viewInDiscoverMessage, - type: 'button', - onClick: async (changePoint) => { - const openInDiscoverUrl = await generateDiscoverUrl(changePoint); - if (typeof openInDiscoverUrl === 'string') { - await application.navigateToUrl(openInDiscoverUrl); - } - }, - enabled: () => discoverUrlError === undefined, - }, - ], - width: ACTIONS_COLUMN_WIDTH, - }, - ]; - - const onChange = useCallback((tableSettings) => { - const { index, size } = tableSettings.page; - const { field, direction } = tableSettings.sort; - - setPageIndex(index); - setPageSize(size); - setSortField(field); - setSortDirection(direction); - }, []); - - const { pagination, pageOfItems, sorting } = useMemo(() => { - const pageStart = pageIndex * pageSize; - const itemCount = changePoints?.length ?? 0; - - let items: ChangePoint[] = changePoints ?? []; - items = sortBy(changePoints, (item) => { - if (item && typeof item[sortField] === 'string') { - // @ts-ignore Object is possibly null or undefined - return item[sortField].toLowerCase(); - } - return item[sortField]; - }); - items = sortDirection === 'asc' ? items : items.reverse(); - - return { - pageOfItems: items.slice(pageStart, pageStart + pageSize), - pagination: { - pageIndex, - pageSize, - totalItemCount: itemCount, - pageSizeOptions: PAGINATION_SIZE_OPTIONS, - }, - sorting: { - sort: { - field: sortField, - direction: sortDirection, - }, - }, - }; - }, [pageIndex, pageSize, sortField, sortDirection, changePoints]); - - // Don't pass on the `loading` state to the table itself because - // it disables hovering events. Because the mini histograms take a while - // to load, hovering would not update the main chart. Instead, - // the loading state is shown by the progress bar on the outer component level. - // The outer component also will display a prompt when no data was returned - // running the analysis and will hide this table. - - return ( - } - rowProps={(changePoint) => { - return { - 'data-test-subj': `aiopsSpikeAnalysisTableRow row-${changePoint.fieldName}-${changePoint.fieldValue}`, - onClick: () => { - if (onPinnedChangePoint) { - onPinnedChangePoint(changePoint); - } - }, - onMouseEnter: () => { - if (onSelectedChangePoint) { - onSelectedChangePoint(changePoint); - } - }, - onMouseLeave: () => { - if (onSelectedChangePoint) { - onSelectedChangePoint(null); - } - }, - style: - selectedChangePoint && - selectedChangePoint.fieldValue === changePoint.fieldValue && - selectedChangePoint.fieldName === changePoint.fieldName - ? { - backgroundColor: euiTheme.euiColorLightestShade, - } - : null, - }; - }} - /> - ); -}; diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx index 7b61911a96384..a7503e7d49111 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx @@ -13,25 +13,50 @@ import { EuiBasicTable, EuiBasicTableColumn, EuiButtonIcon, + EuiIcon, EuiScreenReaderOnly, EuiSpacer, EuiTableSortingType, + EuiToolTip, RIGHT_ALIGNMENT, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { escapeKuery } from '@kbn/es-query'; +import { FormattedMessage } from '@kbn/i18n-react'; import type { ChangePoint } from '@kbn/ml-agg-utils'; + +import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils'; import { useEuiTheme } from '../../hooks/use_eui_theme'; -import { SpikeAnalysisTableExpandedRow } from './spike_analysis_table_expanded_row'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; + +import { MiniHistogram } from '../mini_histogram'; + +import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label'; +import { SpikeAnalysisTable } from './spike_analysis_table'; + +const NARROW_COLUMN_WIDTH = '120px'; +const EXPAND_COLUMN_WIDTH = '40px'; +const ACTIONS_COLUMN_WIDTH = '60px'; +const NOT_AVAILABLE = '--'; const PAGINATION_SIZE_OPTIONS = [5, 10, 20, 50]; -const DEFAULT_SORT_FIELD = 'docCount'; -const DEFAULT_SORT_DIRECTION = 'desc'; -interface GroupTableItem { - id: number; +const DEFAULT_SORT_FIELD = 'pValue'; +const DEFAULT_SORT_DIRECTION = 'asc'; +const viewInDiscoverMessage = i18n.translate( + 'xpack.aiops.spikeAnalysisTable.linksMenu.viewInDiscover', + { + defaultMessage: 'View in Discover', + } +); + +export interface GroupTableItem { + id: string; docCount: number; - group: Record; - repeatedValues: Record; + pValue: number | null; + group: Record; + repeatedValues: Record; + histogram: ChangePoint['histogram']; } interface SpikeAnalysisTableProps { @@ -42,6 +67,7 @@ interface SpikeAnalysisTableProps { onPinnedChangePoint?: (changePoint: ChangePoint | null) => void; onSelectedChangePoint?: (changePoint: ChangePoint | null) => void; selectedChangePoint?: ChangePoint; + onSelectedGroup?: (group: GroupTableItem | null) => void; } export const SpikeAnalysisGroupsTable: FC = ({ @@ -52,6 +78,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ onPinnedChangePoint, onSelectedChangePoint, selectedChangePoint, + onSelectedGroup, }) => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -91,7 +118,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ } itemIdToExpandedRowMapValues[item.id] = ( - = ({ setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); }; + const { application, share, data } = useAiopsAppContext(); + + const discoverLocator = useMemo( + () => share.url.locators.get('DISCOVER_APP_LOCATOR'), + [share.url.locators] + ); + + const discoverUrlError = useMemo(() => { + if (!application.capabilities.discover?.show) { + const discoverNotEnabled = i18n.translate( + 'xpack.aiops.spikeAnalysisTable.discoverNotEnabledErrorMessage', + { + defaultMessage: 'Discover is not enabled', + } + ); + + return discoverNotEnabled; + } + if (!discoverLocator) { + const discoverLocatorMissing = i18n.translate( + 'xpack.aiops.spikeAnalysisTable.discoverLocatorMissingErrorMessage', + { + defaultMessage: 'No locator for Discover detected', + } + ); + + return discoverLocatorMissing; + } + if (!dataViewId) { + const autoGeneratedDiscoverLinkError = i18n.translate( + 'xpack.aiops.spikeAnalysisTable.autoGeneratedDiscoverLinkErrorMessage', + { + defaultMessage: 'Unable to link to Discover; no data view exists for this index', + } + ); + + return autoGeneratedDiscoverLinkError; + } + }, [application.capabilities.discover?.show, dataViewId, discoverLocator]); + + const generateDiscoverUrl = async (groupTableItem: GroupTableItem) => { + if (discoverLocator !== undefined) { + const url = await discoverLocator.getRedirectUrl({ + indexPatternId: dataViewId, + timeRange: data.query.timefilter.timefilter.getTime(), + filters: data.query.filterManager.getFilters(), + query: { + language: SEARCH_QUERY_LANGUAGE.KUERY, + query: [ + ...Object.entries(groupTableItem.group).map( + ([fieldName, fieldValue]) => + `${escapeKuery(fieldName)}:${escapeKuery(String(fieldValue))}` + ), + ...Object.entries(groupTableItem.repeatedValues).map( + ([fieldName, fieldValue]) => + `${escapeKuery(fieldName)}:${escapeKuery(String(fieldValue))}` + ), + ].join(' AND '), + }, + }); + + return url; + } + }; + const columns: Array> = [ { align: RIGHT_ALIGNMENT, - width: '40px', + width: EXPAND_COLUMN_WIDTH, isExpander: true, name: ( @@ -118,18 +210,32 @@ export const SpikeAnalysisGroupsTable: FC = ({ toggleDetails(item)} - aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'} + aria-label={ + itemIdToExpandedRowMap[item.id] + ? i18n.translate( + 'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.collapseAriaLabel', + { + defaultMessage: 'Collapse', + } + ) + : i18n.translate( + 'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.expandAriaLabel', + { + defaultMessage: 'Expand', + } + ) + } iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} /> ), + valign: 'top', }, { 'data-test-subj': 'aiopsSpikeAnalysisGroupsTableColumnGroup', field: 'group', - name: i18n.translate( - 'xpack.aiops.correlations.failedTransactions.correlationsTable.groupLabel', - { defaultMessage: 'Group' } - ), + name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTableGroups.groupLabel', { + defaultMessage: 'Group', + }), render: (_, { group, repeatedValues }) => { const valuesBadges = []; for (const fieldName in group) { @@ -159,7 +265,11 @@ export const SpikeAnalysisGroupsTable: FC = ({ data-test-subj="aiopsSpikeAnalysisGroupsTableColumnGroupBadge" color="hollow" > - +{Object.keys(repeatedValues).length} more + +{Object.keys(repeatedValues).length}{' '} + @@ -169,15 +279,141 @@ export const SpikeAnalysisGroupsTable: FC = ({ }, sortable: false, textOnly: true, + valign: 'top', + }, + { + 'data-test-subj': 'aiopsSpikeAnalysisGroupsTableColumnLogRate', + width: NARROW_COLUMN_WIDTH, + field: 'pValue', + name: ( + + <> + + + + + ), + render: (_, { histogram, id }) => ( + + ), + sortable: false, + valign: 'top', }, { 'data-test-subj': 'aiopsSpikeAnalysisGroupsTableColumnDocCount', + width: NARROW_COLUMN_WIDTH, field: 'docCount', - name: i18n.translate('xpack.aiops.correlations.correlationsTable.docCountLabel', { + name: i18n.translate('xpack.aiops.correlations.spikeAnalysisTableGroups.docCountLabel', { defaultMessage: 'Doc count', }), sortable: true, - width: '20%', + valign: 'top', + }, + { + 'data-test-subj': 'aiopsSpikeAnalysisGroupsTableColumnPValue', + width: NARROW_COLUMN_WIDTH, + field: 'pValue', + name: ( + + <> + + + + + ), + render: (pValue: number | null) => pValue?.toPrecision(3) ?? NOT_AVAILABLE, + sortable: true, + valign: 'top', + }, + { + 'data-test-subj': 'aiopsSpikeAnalysisTableColumnImpact', + width: NARROW_COLUMN_WIDTH, + field: 'pValue', + name: ( + + <> + + + + + ), + render: (_, { pValue }) => { + if (!pValue) return NOT_AVAILABLE; + const label = getFailedTransactionsCorrelationImpactLabel(pValue); + return label ? {label.impact} : null; + }, + sortable: true, + valign: 'top', + }, + { + 'data-test-subj': 'aiOpsSpikeAnalysisTableColumnAction', + name: i18n.translate('xpack.aiops.spikeAnalysisTable.actionsColumnName', { + defaultMessage: 'Actions', + }), + actions: [ + { + name: () => ( + + + + ), + description: viewInDiscoverMessage, + type: 'button', + onClick: async (tableItem) => { + const openInDiscoverUrl = await generateDiscoverUrl(tableItem); + if (typeof openInDiscoverUrl === 'string') { + await application.navigateToUrl(openInDiscoverUrl); + } + }, + enabled: () => discoverUrlError === undefined, + }, + ], + width: ACTIONS_COLUMN_WIDTH, + valign: 'top', }, ]; @@ -228,6 +464,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ compressed columns={columns} items={pageOfItems} + itemId="id" itemIdToExpandedRowMap={itemIdToExpandedRowMap} onChange={onChange} pagination={pagination} @@ -236,6 +473,16 @@ export const SpikeAnalysisGroupsTable: FC = ({ rowProps={(group) => { return { 'data-test-subj': `aiopsSpikeAnalysisGroupsTableRow row-${group.id}`, + onMouseEnter: () => { + if (onSelectedGroup) { + onSelectedGroup(group); + } + }, + onMouseLeave: () => { + if (onSelectedGroup) { + onSelectedGroup(null); + } + }, }; }} /> diff --git a/x-pack/plugins/aiops/public/get_document_stats.ts b/x-pack/plugins/aiops/public/get_document_stats.ts index 1df4e4bd57ae8..07324b5f10605 100644 --- a/x-pack/plugins/aiops/public/get_document_stats.ts +++ b/x-pack/plugins/aiops/public/get_document_stats.ts @@ -14,6 +14,7 @@ import type { ChangePoint } from '@kbn/ml-agg-utils'; import type { Query } from '@kbn/es-query'; import { buildBaseFilterCriteria } from './application/utils/query_utils'; +import { GroupTableItem } from './components/spike_analysis_table/spike_analysis_table_groups'; export interface DocumentCountStats { interval?: number; @@ -34,6 +35,7 @@ export interface DocumentStatsSearchStrategyParams { fieldsToFetch?: string[]; selectedChangePoint?: ChangePoint; includeSelectedChangePoint?: boolean; + selectedGroup?: GroupTableItem | null; } export const getDocumentCountStatsRequest = (params: DocumentStatsSearchStrategyParams) => { @@ -48,6 +50,7 @@ export const getDocumentCountStatsRequest = (params: DocumentStatsSearchStrategy fieldsToFetch, selectedChangePoint, includeSelectedChangePoint, + selectedGroup, } = params; const size = 0; @@ -57,7 +60,8 @@ export const getDocumentCountStatsRequest = (params: DocumentStatsSearchStrategy latestMs, searchQuery, selectedChangePoint, - includeSelectedChangePoint + includeSelectedChangePoint, + selectedGroup ); // Don't use the sampler aggregation as this can lead to some potentially diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index 9ea8f9bd2335b..05736fe00e4fc 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -28,6 +28,7 @@ import { import { useTimefilter } from './use_time_filter'; import { useDocumentCountStats } from './use_document_count_stats'; import type { Dictionary } from './use_url_state'; +import type { GroupTableItem } from '../components/spike_analysis_table/spike_analysis_table_groups'; const DEFAULT_BAR_TARGET = 75; @@ -39,7 +40,8 @@ export const useData = ( aiopsListState: AiOpsIndexBasedAppState, onUpdate: (params: Dictionary) => void, selectedChangePoint?: ChangePoint, - barTarget: number = DEFAULT_BAR_TARGET + barTarget: number = DEFAULT_BAR_TARGET, + selectedGroup?: GroupTableItem | null ) => { const { uiSettings, @@ -109,15 +111,25 @@ export const useData = ( const overallStatsRequest = useMemo(() => { return fieldStatsRequest - ? { ...fieldStatsRequest, selectedChangePoint, includeSelectedChangePoint: false } + ? { + ...fieldStatsRequest, + selectedChangePoint, + selectedGroup, + includeSelectedChangePoint: false, + } : undefined; - }, [fieldStatsRequest, selectedChangePoint]); + }, [fieldStatsRequest, selectedChangePoint, selectedGroup]); const selectedChangePointStatsRequest = useMemo(() => { - return fieldStatsRequest && selectedChangePoint - ? { ...fieldStatsRequest, selectedChangePoint, includeSelectedChangePoint: true } + return fieldStatsRequest && (selectedChangePoint || selectedGroup) + ? { + ...fieldStatsRequest, + selectedChangePoint, + selectedGroup, + includeSelectedChangePoint: true, + } : undefined; - }, [fieldStatsRequest, selectedChangePoint]); + }, [fieldStatsRequest, selectedChangePoint, selectedGroup]); const documentStats = useDocumentCountStats( overallStatsRequest, diff --git a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts index f0fadf9476e74..48ea2dbddb1c3 100644 --- a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts +++ b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts @@ -18,10 +18,12 @@ import type { DataRequestHandlerContext } from '@kbn/data-plugin/server'; import { streamFactory } from '@kbn/aiops-utils'; import type { ChangePoint, NumericChartData, NumericHistogramField } from '@kbn/ml-agg-utils'; import { fetchHistogramsForFields } from '@kbn/ml-agg-utils'; +import { stringHash } from '@kbn/ml-string-hash'; import { addChangePointsAction, addChangePointsGroupAction, + addChangePointsGroupHistogramAction, addChangePointsHistogramAction, aiopsExplainLogRateSpikesSchema, addErrorAction, @@ -35,8 +37,14 @@ import type { AiopsLicense } from '../types'; import { fetchChangePointPValues } from './queries/fetch_change_point_p_values'; import { fetchFieldCandidates } from './queries/fetch_field_candidates'; -import { fetchFrequentItems } from './queries/fetch_frequent_items'; import { + dropDuplicates, + fetchFrequentItems, + groupDuplicates, +} from './queries/fetch_frequent_items'; +import type { ItemsetResult } from './queries/fetch_frequent_items'; +import { + getFieldValuePairCounts, getSimpleHierarchicalTree, getSimpleHierarchicalTreeLeaves, markDuplicates, @@ -210,48 +218,232 @@ export const defineExplainLogRateSpikesRoute = ( return; } + const histogramFields: [NumericHistogramField] = [ + { fieldName: request.body.timeFieldName, type: KBN_FIELD_TYPES.DATE }, + ]; + + const [overallTimeSeries] = (await fetchHistogramsForFields( + client, + request.body.index, + { match_all: {} }, + // fields + histogramFields, + // samplerShardSize + -1, + undefined + )) as [NumericChartData]; + if (groupingEnabled) { + // To optimize the `frequent_items` query, we identify duplicate change points by count attributes. + // Note this is a compromise and not 100% accurate because there could be change points that + // have the exact same counts but still don't co-occur. + const duplicateIdentifier: Array = [ + 'doc_count', + 'bg_count', + 'total_doc_count', + 'total_bg_count', + ]; + + // These are the deduplicated change points we pass to the `frequent_items` aggregation. + const deduplicatedChangePoints = dropDuplicates(changePoints, duplicateIdentifier); + + // We use the grouped change points to later repopulate + // the `frequent_items` result with the missing duplicates. + const groupedChangePoints = groupDuplicates(changePoints, duplicateIdentifier).filter( + (g) => g.group.length > 1 + ); + const { fields, df } = await fetchFrequentItems( client, request.body.index, JSON.parse(request.body.searchQuery) as estypes.QueryDslQueryContainer, - changePoints, + deduplicatedChangePoints, request.body.timeFieldName, request.body.deviationMin, request.body.deviationMax ); - // Filter itemsets by significant change point field value pairs - const filteredDf = df.filter((fi) => { - const { set: currentItems } = fi; + // The way the `frequent_items` aggregations works could return item sets that include + // field/value pairs that are not part of the original list of significant change points. + // This cleans up groups and removes those unrelated field/value pairs. + const filteredDf = df + .map((fi) => { + fi.set = Object.entries(fi.set).reduce( + (set, [field, value]) => { + if ( + changePoints.some((cp) => cp.fieldName === field && cp.fieldValue === value) + ) { + set[field] = value; + } + return set; + }, + {} + ); + fi.size = Object.keys(fi.set).length; + return fi; + }) + .filter((fi) => fi.size > 1); + + // `frequent_items` returns lot of different small groups of field/value pairs that co-occur. + // The following steps analyse these small groups, identify overlap between these groups, + // and then summarize them in larger groups where possible. + + // Get a tree structure based on `frequent_items`. + const { root } = getSimpleHierarchicalTree(filteredDf, true, false, fields); + + // Each leave of the tree will be a summarized group of co-occuring field/value pairs. + const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []); + + // To be able to display a more cleaned up results table in the UI, we identify field/value pairs + // that occur in multiple groups. This will allow us to highlight field/value pairs that are + // unique to a group in a better way. This step will also re-add duplicates we identified in the + // beginning and didn't pass on to the `frequent_items` agg. + const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves); + const changePointGroups = markDuplicates(treeLeaves, fieldValuePairCounts).map((g) => { + const group = [...g.group]; + + for (const groupItem of g.group) { + const { duplicate } = groupItem; + const duplicates = groupedChangePoints.find((d) => + d.group.some( + (dg) => + dg.fieldName === groupItem.fieldName && dg.fieldValue === groupItem.fieldValue + ) + ); - return Object.entries(currentItems).every(([key, value]) => { - return changePoints.some((cp) => { - return cp.fieldName === key && cp.fieldValue === value; - }); + if (duplicates !== undefined) { + group.push( + ...duplicates.group.map((d) => { + return { + fieldName: d.fieldName, + fieldValue: d.fieldValue, + duplicate, + }; + }) + ); + } + } + + return { + ...g, + group, + }; + }); + + // Some field/value pairs might not be part of the `frequent_items` result set, for example + // because they don't co-occur with other field/value pairs or because of the limits we set on the query. + // In this next part we identify those missing pairs and add them as individual groups. + const missingChangePoints = deduplicatedChangePoints.filter((cp) => { + return !changePointGroups.some((cpg) => { + return cpg.group.some( + (d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue + ); }); }); - const { root } = getSimpleHierarchicalTree(filteredDf, true, false, fields); - const changePointsGroups = getSimpleHierarchicalTreeLeaves(root, []); + changePointGroups.push( + ...missingChangePoints.map(({ fieldName, fieldValue, doc_count: docCount, pValue }) => { + const duplicates = groupedChangePoints.find((d) => + d.group.some((dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue) + ); + if (duplicates !== undefined) { + return { + id: `${stringHash( + JSON.stringify( + duplicates.group.map((d) => ({ + fieldName: d.fieldName, + fieldValue: d.fieldValue, + })) + ) + )}`, + group: duplicates.group.map((d) => ({ + fieldName: d.fieldName, + fieldValue: d.fieldValue, + duplicate: false, + })), + docCount, + pValue, + }; + } else { + return { + id: `${stringHash(JSON.stringify({ fieldName, fieldValue }))}`, + group: [ + { + fieldName, + fieldValue, + duplicate: false, + }, + ], + docCount, + pValue, + }; + } + }) + ); - push(addChangePointsGroupAction(markDuplicates(changePointsGroups))); - } + // Finally, we'll find out if there's at least one group with at least two items, + // only then will we return the groups to the clients and make the grouping option available. + const maxItems = Math.max(...changePointGroups.map((g) => g.group.length)); - const histogramFields: [NumericHistogramField] = [ - { fieldName: request.body.timeFieldName, type: KBN_FIELD_TYPES.DATE }, - ]; + if (maxItems > 1) { + push(addChangePointsGroupAction(changePointGroups)); + } - const [overallTimeSeries] = (await fetchHistogramsForFields( - client, - request.body.index, - { match_all: {} }, - // fields - histogramFields, - // samplerShardSize - -1, - undefined - )) as [NumericChartData]; + if (changePointGroups) { + await asyncForEach(changePointGroups, async (cpg, index) => { + const histogramQuery = { + bool: { + filter: cpg.group.map((d) => ({ + term: { [d.fieldName]: d.fieldValue }, + })), + }, + }; + + const [cpgTimeSeries] = (await fetchHistogramsForFields( + client, + request.body.index, + histogramQuery, + // fields + [ + { + fieldName: request.body.timeFieldName, + type: KBN_FIELD_TYPES.DATE, + interval: overallTimeSeries.interval, + min: overallTimeSeries.stats[0], + max: overallTimeSeries.stats[1], + }, + ], + // samplerShardSize + -1, + undefined + )) as [NumericChartData]; + + const histogram = + overallTimeSeries.data.map((o, i) => { + const current = cpgTimeSeries.data.find( + (d1) => d1.key_as_string === o.key_as_string + ) ?? { + doc_count: 0, + }; + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_change_point: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + }) ?? []; + + push( + addChangePointsGroupHistogramAction([ + { + id: cpg.id, + histogram, + }, + ]) + ); + }); + } + } // time series filtered by fields if (changePoints) { diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts index 02d20ba18795c..055c22397064f 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts @@ -10,7 +10,7 @@ import { uniq, uniqWith, pick, isEqual } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { ChangePoint } from '@kbn/ml-agg-utils'; +import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils'; interface FrequentItemsAggregation extends estypes.AggregationsSamplerAggregation { fi: { @@ -18,8 +18,32 @@ interface FrequentItemsAggregation extends estypes.AggregationsSamplerAggregatio }; } -function dropDuplicates(cp: ChangePoint[], uniqueFields: string[]) { - return uniqWith(cp, (a, b) => isEqual(pick(a, uniqueFields), pick(b, uniqueFields))); +export function dropDuplicates(cps: ChangePoint[], uniqueFields: Array) { + return uniqWith(cps, (a, b) => isEqual(pick(a, uniqueFields), pick(b, uniqueFields))); +} + +interface ChangePointDuplicateGroup { + keys: Pick; + group: ChangePoint[]; +} +export function groupDuplicates(cps: ChangePoint[], uniqueFields: Array) { + const groups: ChangePointDuplicateGroup[] = []; + + for (const cp of cps) { + const compareAttributes = pick(cp, uniqueFields); + + const groupIndex = groups.findIndex((g) => isEqual(g.keys, compareAttributes)); + if (groupIndex === -1) { + groups.push({ + keys: compareAttributes, + group: [cp], + }); + } else { + groups[groupIndex].group.push(cp); + } + } + + return groups; } export async function fetchFrequentItems( @@ -31,17 +55,8 @@ export async function fetchFrequentItems( deviationMin: number, deviationMax: number ) { - // first remove duplicates in sig terms - note this is not strictly perfect as there could - // be conincidentally equal counts, but in general is ok... - const terms = dropDuplicates(changePoints, [ - 'doc_count', - 'bg_count', - 'total_doc_count', - 'total_bg_count', - ]); - // get unique fields that are left - const fields = [...new Set(terms.map((t) => t.fieldName))]; + const fields = [...new Set(changePoints.map((t) => t.fieldName))]; // TODO add query params const query = { @@ -58,7 +73,7 @@ export async function fetchFrequentItems( }, }, ], - should: terms.map((t) => { + should: changePoints.map((t) => { return { term: { [t.fieldName]: t.fieldValue } }; }), }, @@ -68,7 +83,7 @@ export async function fetchFrequentItems( field, })); - const totalDocCount = terms[0].total_doc_count; + const totalDocCount = changePoints[0].total_doc_count; const minDocCount = 50000; let sampleProbability = 1; @@ -88,7 +103,7 @@ export async function fetchFrequentItems( frequent_items: { minimum_set_size: 2, size: 200, - minimum_support: 0.1, + minimum_support: 0.01, fields: aggFields, }, }, @@ -153,7 +168,7 @@ export async function fetchFrequentItems( return; } - result.size = Object.keys(result).length; + result.size = Object.keys(result.set).length; result.maxPValue = maxPValue; result.doc_count = fis.doc_count; result.support = fis.support; @@ -162,15 +177,21 @@ export async function fetchFrequentItems( results.push(result); }); + results.sort((a, b) => { + return b.doc_count - a.doc_count; + }); + + const uniqueFields = uniq(results.flatMap((r) => Object.keys(r.set))); + return { - fields: uniq(results.flatMap((r) => Object.keys(r.set))), + fields: uniqueFields, df: results, totalDocCount: totalDocCountFi, }; } export interface ItemsetResult { - set: Record; + set: Record; size: number; maxPValue: number; doc_count: number; diff --git a/x-pack/plugins/aiops/server/routes/queries/get_simple_hierarchical_tree.test.ts b/x-pack/plugins/aiops/server/routes/queries/get_simple_hierarchical_tree.test.ts new file mode 100644 index 0000000000000..5f2125a583db7 --- /dev/null +++ b/x-pack/plugins/aiops/server/routes/queries/get_simple_hierarchical_tree.test.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ChangePointGroup } from '@kbn/ml-agg-utils'; + +import { getFieldValuePairCounts, markDuplicates } from './get_simple_hierarchical_tree'; + +const changePointGroups: ChangePointGroup[] = [ + { + id: 'group-1', + group: [ + { + fieldName: 'custom_field.keyword', + fieldValue: 'deviation', + }, + { + fieldName: 'airline', + fieldValue: 'UAL', + }, + ], + docCount: 101, + pValue: 0.01, + }, + { + id: 'group-2', + group: [ + { + fieldName: 'custom_field.keyword', + fieldValue: 'deviation', + }, + { + fieldName: 'airline', + fieldValue: 'AAL', + }, + ], + docCount: 49, + pValue: 0.001, + }, +]; + +describe('get_simple_hierarchical_tree', () => { + describe('getFieldValuePairCounts', () => { + it('returns a nested record with field/value pair counts', () => { + const fieldValuePairCounts = getFieldValuePairCounts(changePointGroups); + + expect(fieldValuePairCounts).toEqual({ + airline: { + AAL: 1, + UAL: 1, + }, + 'custom_field.keyword': { + deviation: 2, + }, + }); + }); + }); + + describe('markDuplicates', () => { + it('marks duplicates based on change point groups', () => { + const fieldValuePairCounts = getFieldValuePairCounts(changePointGroups); + const markedDuplicates = markDuplicates(changePointGroups, fieldValuePairCounts); + + expect(markedDuplicates).toEqual([ + { + id: 'group-1', + group: [ + { + fieldName: 'custom_field.keyword', + fieldValue: 'deviation', + duplicate: true, + }, + { + fieldName: 'airline', + fieldValue: 'UAL', + duplicate: false, + }, + ], + docCount: 101, + pValue: 0.01, + }, + { + id: 'group-2', + group: [ + { + fieldName: 'custom_field.keyword', + fieldValue: 'deviation', + duplicate: true, + }, + { + fieldName: 'airline', + fieldValue: 'AAL', + duplicate: false, + }, + ], + docCount: 49, + pValue: 0.001, + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/aiops/server/routes/queries/get_simple_hierarchical_tree.ts b/x-pack/plugins/aiops/server/routes/queries/get_simple_hierarchical_tree.ts index 79ad876ab6453..9f39d1eb11f68 100644 --- a/x-pack/plugins/aiops/server/routes/queries/get_simple_hierarchical_tree.ts +++ b/x-pack/plugins/aiops/server/routes/queries/get_simple_hierarchical_tree.ts @@ -5,21 +5,21 @@ * 2.0. */ -import { max } from 'd3-array'; // import { omit, uniq } from 'lodash'; import type { ChangePointGroup, FieldValuePair } from '@kbn/ml-agg-utils'; +import { stringHash } from '@kbn/ml-string-hash'; import type { ItemsetResult } from './fetch_frequent_items'; function getValueCounts(df: ItemsetResult[], field: string) { - return df.reduce((p, c) => { + return df.reduce>((p, c) => { if (c.set[field] === undefined) { return p; } p[c.set[field]] = p[c.set[field]] ? p[c.set[field]] + 1 : 1; return p; - }, {} as Record); + }, {}); } function getValuesDescending(df: ItemsetResult[], field: string): string[] { @@ -35,6 +35,7 @@ interface NewNode { name: string; set: FieldValuePair[]; docCount: number; + pValue: number | null; children: NewNode[]; icon: string; iconStyle: string; @@ -52,6 +53,7 @@ function NewNodeFactory(name: string): NewNode { name, set: [], docCount: 0, + pValue: 0, children, icon: 'default', iconStyle: 'default', @@ -88,8 +90,8 @@ function dfDepthFirstSearch( displayOther: boolean ) { const filteredItemSets = iss.filter((is) => { - for (const [key, values] of Object.entries(is.set)) { - if (key === field && values.includes(value)) { + for (const [key, setValue] of Object.entries(is.set)) { + if (key === field && setValue === value) { return true; } } @@ -100,8 +102,9 @@ function dfDepthFirstSearch( return 0; } - const docCount = max(filteredItemSets.map((fis) => fis.doc_count)) ?? 0; - const totalDocCount = max(filteredItemSets.map((fis) => fis.total_doc_count)) ?? 0; + const docCount = Math.max(...filteredItemSets.map((fis) => fis.doc_count)); + const pValue = Math.max(...filteredItemSets.map((fis) => fis.maxPValue)); + const totalDocCount = Math.max(...filteredItemSets.map((fis) => fis.total_doc_count)); let label = `${parentLabel} ${value}`; @@ -111,6 +114,7 @@ function dfDepthFirstSearch( displayParent.name += ` ${value}`; displayParent.set.push({ fieldName: field, fieldValue: value }); displayParent.docCount = docCount; + displayParent.pValue = pValue; displayNode = displayParent; } else { displayNode = NewNodeFactory(`${docCount}/${totalDocCount}${label}`); @@ -118,6 +122,7 @@ function dfDepthFirstSearch( displayNode.set = [...displayParent.set]; displayNode.set.push({ fieldName: field, fieldValue: value }); displayNode.docCount = docCount; + displayNode.pValue = pValue; displayParent.addNode(displayNode); } @@ -145,6 +150,7 @@ function dfDepthFirstSearch( nextDisplayNode.iconStyle = 'warning'; nextDisplayNode.set = displayNode.set; nextDisplayNode.docCount = docCount; + nextDisplayNode.pValue = pValue; displayNode.addNode(nextDisplayNode); displayNode = nextDisplayNode; } @@ -196,7 +202,7 @@ export function getSimpleHierarchicalTree( const field = fields[0]; - const totalDocCount = max(df.map((d) => d.total_doc_count)) ?? 0; + const totalDocCount = Math.max(...df.map((d) => d.total_doc_count)); const newRoot = NewNodeFactory(''); @@ -225,9 +231,13 @@ export function getSimpleHierarchicalTreeLeaves( leaves: ChangePointGroup[], level = 1 ) { - // console.log(`${'-'.repeat(level)} ${tree.name} ${tree.children.length}`); if (tree.children.length === 0) { - leaves.push({ group: tree.set, docCount: tree.docCount }); + leaves.push({ + id: `${stringHash(JSON.stringify(tree.set))}`, + group: tree.set, + docCount: tree.docCount, + pValue: tree.pValue, + }); } else { for (const child of tree.children) { const newLeaves = getSimpleHierarchicalTreeLeaves(child, [], level + 1); @@ -237,29 +247,43 @@ export function getSimpleHierarchicalTreeLeaves( } } + if (leaves.length === 1 && leaves[0].group.length === 0 && leaves[0].docCount === 0) { + return []; + } + return leaves; } +type FieldValuePairCounts = Record>; /** - * Analyse duplicate field/value pairs in change point groups. + * Get a nested record of field/value pairs with counts */ -export function markDuplicates(cpgs: ChangePointGroup[]): ChangePointGroup[] { - const fieldValuePairCounts: Record = {}; - cpgs.forEach((cpg) => { - cpg.group.forEach((g) => { - const str = `${g.fieldName}$$$$${g.fieldValue}`; - fieldValuePairCounts[str] = fieldValuePairCounts[str] ? fieldValuePairCounts[str] + 1 : 1; +export function getFieldValuePairCounts(cpgs: ChangePointGroup[]): FieldValuePairCounts { + return cpgs.reduce((p, { group }) => { + group.forEach(({ fieldName, fieldValue }) => { + if (p[fieldName] === undefined) { + p[fieldName] = {}; + } + p[fieldName][fieldValue] = p[fieldName][fieldValue] ? p[fieldName][fieldValue] + 1 : 1; }); - }); + return p; + }, {}); +} +/** + * Analyse duplicate field/value pairs in change point groups. + */ +export function markDuplicates( + cpgs: ChangePointGroup[], + fieldValuePairCounts: FieldValuePairCounts +): ChangePointGroup[] { return cpgs.map((cpg) => { return { ...cpg, group: cpg.group.map((g) => { - const str = `${g.fieldName}$$$$${g.fieldValue}`; return { ...g, - duplicate: fieldValuePairCounts[str] > 1, + duplicate: fieldValuePairCounts[g.fieldName][g.fieldValue] > 1, }; }), }; diff --git a/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts b/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts index 6e6eb3cb43f0d..5efd2d7a49152 100644 --- a/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts +++ b/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts @@ -116,6 +116,13 @@ const byStatusSchema: MakeSchemaFrom['count_rules_by_execution_st warning: { type: 'long' }, }; +const byStatusPerDaySchema: MakeSchemaFrom['count_rules_by_execution_status_per_day'] = + { + success: { type: 'long' }, + failure: { type: 'long' }, + unknown: { type: 'long' }, + }; + const byNotifyWhenSchema: MakeSchemaFrom['count_rules_by_notify_when'] = { on_action_group_change: { type: 'long' }, on_active_alert: { type: 'long' }, @@ -200,6 +207,7 @@ export function createAlertingUsageCollector( count_rules_muted: 0, count_rules_with_muted_alerts: 0, count_connector_types_by_consumers: {}, + count_rules_by_execution_status_per_day: {}, avg_execution_time_per_day: 0, avg_execution_time_by_type_per_day: {}, avg_es_search_duration_per_day: 0, @@ -283,6 +291,7 @@ export function createAlertingUsageCollector( count_rules_muted: { type: 'long' }, count_rules_with_muted_alerts: { type: 'long' }, count_connector_types_by_consumers: { DYNAMIC_KEY: { DYNAMIC_KEY: { type: 'long' } } }, + count_rules_by_execution_status_per_day: byStatusPerDaySchema, avg_execution_time_per_day: { type: 'long' }, avg_execution_time_by_type_per_day: byTypeSchema, avg_es_search_duration_per_day: { type: 'long' }, diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_event_log.test.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_event_log.test.ts index f3e21f6a161fa..64bc0ae8be0fb 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_event_log.test.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_event_log.test.ts @@ -1291,6 +1291,14 @@ describe('event log telemetry', () => { avg_total_search_duration: { value: 28.630434782608695, }, + by_execution_status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'success', doc_count: 21 }, + { key: 'failure', doc_count: 22 }, + ], + }, }, }); @@ -1377,7 +1385,6 @@ describe('event log telemetry', () => { logs__alert__document__count: 0, }, }, - alertsPercentilesByType: { p50: { '__index-threshold': 1, @@ -1398,6 +1405,10 @@ describe('event log telemetry', () => { logs__alert__document__count: 0, }, }, + countRulesByExecutionStatus: { + failure: 22, + success: 21, + }, hasErrors: false, }); }); @@ -1437,6 +1448,7 @@ describe('event log telemetry', () => { generatedActionsPercentilesByType: {}, alertsPercentiles: {}, alertsPercentilesByType: {}, + countRulesByExecutionStatus: {}, }); }); }); diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_event_log.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_event_log.ts index 703d579e66c25..e21df2376f4f8 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_event_log.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_event_log.ts @@ -54,15 +54,14 @@ interface GetExecutionsPerDayCountResults { generatedActionsPercentilesByType: Record>; alertsPercentiles: Record; alertsPercentilesByType: Record>; + countRulesByExecutionStatus: Record; } - interface GetExecutionTimeoutsPerDayCountResults { hasErrors: boolean; errorMessage?: string; countExecutionTimeouts: number; countExecutionTimeoutsByType: Record; } - interface GetExecutionCountsExecutionFailures extends AggregationsSingleBucketAggregateBase { by_reason: AggregationsTermsAggregateBase; } @@ -145,6 +144,11 @@ export async function getExecutionsPerDayCount({ }, aggs: eventLogAggs, }, + by_execution_status: { + terms: { + field: 'event.outcome', + }, + }, }, }, }; @@ -165,6 +169,7 @@ export async function getExecutionsPerDayCount({ avg_execution_time: AggregationsSingleMetricAggregateBase; avg_es_search_duration: AggregationsSingleMetricAggregateBase; avg_total_search_duration: AggregationsSingleMetricAggregateBase; + by_execution_status: AggregationsTermsAggregateBase; }; const aggregationsByRuleTypeId: AggregationsBuckets = @@ -176,6 +181,9 @@ export async function getExecutionsPerDayCount({ ...parseExecutionFailureByRuleType(aggregationsByRuleTypeId), ...parseExecutionCountAggregationResults(aggregations), countTotalRuleExecutions: totalRuleExecutions ?? 0, + countRulesByExecutionStatus: parseSimpleRuleTypeBucket( + aggregations.by_execution_status.buckets + ), }; } catch (err) { const errorMessage = err && err.message ? err.message : err.toString(); @@ -204,6 +212,7 @@ export async function getExecutionsPerDayCount({ generatedActionsPercentilesByType: {}, alertsPercentiles: {}, alertsPercentilesByType: {}, + countRulesByExecutionStatus: {}, }; } } @@ -313,6 +322,14 @@ export async function getExecutionTimeoutsPerDayCount({ * avg_total_search_duration: { // average total search duration across executions * value: 43.74647887323944, * }, + * by_execution_status: { + * "doc_count_error_upper_bound":0, + * "sum_other_doc_count":0, + * "buckets":[ + * {"key":"success","doc_count":48}, + * {"key":"failure","doc_count":1} + * ] + * } * } */ diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index 4f835f3c4a64f..ab20844948011 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -161,6 +161,8 @@ export function telemetryTaskRunner( dailyExecutionCounts.countFailedExecutionsByReason, count_rules_executions_failured_by_reason_by_type_per_day: dailyExecutionCounts.countFailedExecutionsByReasonByType, + count_rules_by_execution_status_per_day: + dailyExecutionCounts.countRulesByExecutionStatus, count_rules_executions_timeouts_per_day: dailyExecutionTimeoutCounts.countExecutionTimeouts, count_rules_executions_timeouts_by_type_per_day: diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerting/server/usage/types.ts index 29a32de29bc1a..15c0f0a962710 100644 --- a/x-pack/plugins/alerting/server/usage/types.ts +++ b/x-pack/plugins/alerting/server/usage/types.ts @@ -42,6 +42,7 @@ export interface AlertingUsage { count_rules_snoozed: number; count_rules_muted: number; count_rules_with_muted_alerts: number; + count_rules_by_execution_status_per_day: Record; percentile_num_generated_actions_per_day: { p50: number; p90: number; diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 3ac48a1e367ce..95c36d24aad5b 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -221,6 +221,10 @@ exports[`Error TRANSACTION_DURATION 1`] = `undefined`; exports[`Error TRANSACTION_DURATION_HISTOGRAM 1`] = `undefined`; +exports[`Error TRANSACTION_DURATION_SUMMARY 1`] = `undefined`; + +exports[`Error TRANSACTION_FAILURE_COUNT 1`] = `undefined`; + exports[`Error TRANSACTION_ID 1`] = `"transaction id"`; exports[`Error TRANSACTION_NAME 1`] = `undefined`; @@ -233,6 +237,8 @@ exports[`Error TRANSACTION_ROOT 1`] = `undefined`; exports[`Error TRANSACTION_SAMPLED 1`] = `undefined`; +exports[`Error TRANSACTION_SUCCESS_COUNT 1`] = `undefined`; + exports[`Error TRANSACTION_TYPE 1`] = `"request"`; exports[`Error TRANSACTION_URL 1`] = `undefined`; @@ -462,6 +468,10 @@ exports[`Span TRANSACTION_DURATION 1`] = `undefined`; exports[`Span TRANSACTION_DURATION_HISTOGRAM 1`] = `undefined`; +exports[`Span TRANSACTION_DURATION_SUMMARY 1`] = `undefined`; + +exports[`Span TRANSACTION_FAILURE_COUNT 1`] = `undefined`; + exports[`Span TRANSACTION_ID 1`] = `"transaction id"`; exports[`Span TRANSACTION_NAME 1`] = `undefined`; @@ -474,6 +484,8 @@ exports[`Span TRANSACTION_ROOT 1`] = `undefined`; exports[`Span TRANSACTION_SAMPLED 1`] = `undefined`; +exports[`Span TRANSACTION_SUCCESS_COUNT 1`] = `undefined`; + exports[`Span TRANSACTION_TYPE 1`] = `undefined`; exports[`Span TRANSACTION_URL 1`] = `undefined`; @@ -721,6 +733,10 @@ exports[`Transaction TRANSACTION_DURATION 1`] = `1337`; exports[`Transaction TRANSACTION_DURATION_HISTOGRAM 1`] = `undefined`; +exports[`Transaction TRANSACTION_DURATION_SUMMARY 1`] = `undefined`; + +exports[`Transaction TRANSACTION_FAILURE_COUNT 1`] = `undefined`; + exports[`Transaction TRANSACTION_ID 1`] = `"transaction id"`; exports[`Transaction TRANSACTION_NAME 1`] = `"transaction name"`; @@ -733,6 +749,8 @@ exports[`Transaction TRANSACTION_ROOT 1`] = `undefined`; exports[`Transaction TRANSACTION_SAMPLED 1`] = `true`; +exports[`Transaction TRANSACTION_SUCCESS_COUNT 1`] = `undefined`; + exports[`Transaction TRANSACTION_TYPE 1`] = `"transaction type"`; exports[`Transaction TRANSACTION_URL 1`] = `"http://www.elastic.co"`; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index 575588018b369..f29bf3c607e86 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -46,12 +46,15 @@ export const PROCESSOR_EVENT = 'processor.event'; export const TRANSACTION_DURATION = 'transaction.duration.us'; export const TRANSACTION_DURATION_HISTOGRAM = 'transaction.duration.histogram'; +export const TRANSACTION_DURATION_SUMMARY = 'transaction.duration.summary'; export const TRANSACTION_TYPE = 'transaction.type'; export const TRANSACTION_RESULT = 'transaction.result'; export const TRANSACTION_NAME = 'transaction.name'; export const TRANSACTION_ID = 'transaction.id'; export const TRANSACTION_SAMPLED = 'transaction.sampled'; export const TRANSACTION_PAGE_URL = 'transaction.page.url'; +export const TRANSACTION_FAILURE_COUNT = 'transaction.failure_count'; +export const TRANSACTION_SUCCESS_COUNT = 'transaction.success_count'; // for transaction metrics export const TRANSACTION_ROOT = 'transaction.root'; diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 1fff65f4746ad..c3cf929a40209 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -64,7 +64,15 @@ export const renderApp = ({ element.classList.add(APP_WRAPPER_CLASS); ReactDOM.render( - + - {setting.description} + {setting.description} {setting.defaultValue && ( <> diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/settings_page/settings_page.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/settings_page/settings_page.tsx index f4e8f52cdc1df..94357cf4fb63f 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/settings_page/settings_page.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_configurations/agent_configuration_create_edit/settings_page/settings_page.tsx @@ -6,24 +6,20 @@ */ import { - EuiBottomBar, EuiButton, - EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiForm, - EuiHealth, + EuiHorizontalRule, EuiLoadingSpinner, EuiSpacer, EuiStat, - EuiText, - EuiHorizontalRule, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useUiTracker } from '@kbn/observability-plugin/public'; import React, { useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUiTracker } from '@kbn/observability-plugin/public'; import { getOptionLabel } from '../../../../../../../common/agent_configuration/all_option'; import { AgentConfigurationIntake } from '../../../../../../../common/agent_configuration/configuration_types'; import { @@ -34,6 +30,7 @@ import { import { AgentName } from '../../../../../../../typings/es_schemas/ui/fields/agent'; import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; import { FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; +import { BottomBarActions } from '../../../bottom_bar_actions'; import { saveConfig } from './save_config'; import { SettingFormRow } from './setting_form_row'; @@ -190,53 +187,16 @@ export function SettingsPage({ {/* Bottom bar with save button */} {unsavedChangesCount > 0 && ( - - - - - - {i18n.translate('xpack.apm.unsavedChanges', { - defaultMessage: - '{unsavedChangesCount, plural, =0{0 unsaved changes} one {1 unsaved change} other {# unsaved changes}} ', - values: { unsavedChangesCount }, - })} - - - - - - - {i18n.translate( - 'xpack.apm.agentConfig.settingsPage.discardChangesButton', - { defaultMessage: 'Discard changes' } - )} - - - - - {i18n.translate( - 'xpack.apm.agentConfig.settingsPage.saveButton', - { defaultMessage: 'Save configuration' } - )} - - - - - - + )} ); diff --git a/x-pack/plugins/apm/public/components/app/settings/bottom_bar_actions/index.tsx b/x-pack/plugins/apm/public/components/app/settings/bottom_bar_actions/index.tsx new file mode 100644 index 0000000000000..85ee597b754ec --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/settings/bottom_bar_actions/index.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 { + EuiBottomBar, + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +interface Props { + unsavedChangesCount: number; + isLoading: boolean; + onDiscardChanges: () => void; + onSave: () => void; + saveLabel: string; +} + +export function BottomBarActions({ + isLoading, + onDiscardChanges, + onSave, + unsavedChangesCount, + saveLabel, +}: Props) { + return ( + + + + + + {i18n.translate('xpack.apm.bottomBarActions.unsavedChanges', { + defaultMessage: + '{unsavedChangesCount, plural, =0{0 unsaved changes} one {1 unsaved change} other {# unsaved changes}} ', + values: { unsavedChangesCount }, + })} + + + + + + + {i18n.translate( + 'xpack.apm.bottomBarActions.discardChangesButton', + { + defaultMessage: 'Discard changes', + } + )} + + + + + {saveLabel} + + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx index ef41b75aedeaf..d8e62397626f8 100644 --- a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { EuiButton } from '@elastic/eui'; import { LazyField } from '@kbn/advanced-settings-plugin/public'; import { i18n } from '@kbn/i18n'; import { @@ -20,6 +19,7 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useApmEditableSettings } from '../../../../hooks/use_apm_editable_settings'; +import { BottomBarActions } from '../bottom_bar_actions'; const apmSettingsKeys = [ enableComparisonByDefault, @@ -38,6 +38,7 @@ export function GeneralSettings() { unsavedChanges, saveAll, isSaving, + cleanUnsavedChanges, } = useApmEditableSettings(apmSettingsKeys); async function handleSave() { @@ -76,16 +77,17 @@ export function GeneralSettings() { /> ); })} - - {i18n.translate('xpack.apm.labs.reload', { - defaultMessage: 'Reload to apply changes', - })} - + {!isEmpty(unsavedChanges) && ( + + )} ); } diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx index ad16bd7302220..9b62ae8e6da89 100644 --- a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import React from 'react'; import { EuiButton, EuiFlexGroup, @@ -14,7 +15,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; -import React from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ApmPluginStartDeps } from '../../../../plugin'; import { TraceSearchQuery, TraceSearchType, @@ -56,7 +58,12 @@ export function TraceSearchBox({ error, loading, }: Props) { - const { unifiedSearch } = useApmPluginContext(); + const { unifiedSearch, core, data } = useApmPluginContext(); + const { notifications, http, docLinks, uiSettings } = core; + const { + services: { storage }, + } = useKibana(); + const { dataView } = useApmDataView(); return ( @@ -133,6 +140,21 @@ export function TraceSearchBox({ query: String(e.query ?? ''), }); }} + appName={i18n.translate( + 'xpack.apm.traceExplorer.appName', + { + defaultMessage: 'APM', + } + )} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + }} /> )} diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx index 9622acb525618..3f84a4bdb1347 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx @@ -14,10 +14,11 @@ import { import { i18n } from '@kbn/i18n'; import { omit } from 'lodash'; import React from 'react'; +import { enableAwsLambdaMetrics } from '@kbn/observability-plugin/common'; import { - isMobileAgentName, isJavaAgentName, isJRubyAgent, + isMobileAgentName, isRumAgentName, isServerlessAgent, } from '../../../../../common/agent_name'; @@ -29,13 +30,13 @@ import { ServiceAnomalyTimeseriesContextProvider } from '../../../../context/ser import { useApmParams } from '../../../../hooks/use_apm_params'; import { useApmRouter } from '../../../../hooks/use_apm_router'; import { useTimeRange } from '../../../../hooks/use_time_range'; +import { getAlertingCapabilities } from '../../../alerting/get_alerting_capabilities'; +import { BetaBadge } from '../../../shared/beta_badge'; import { SearchBar } from '../../../shared/search_bar'; import { ServiceIcons } from '../../../shared/service_icons'; +import { TechnicalPreviewBadge } from '../../../shared/technical_preview_badge'; import { ApmMainTemplate } from '../apm_main_template'; import { AnalyzeDataButton } from './analyze_data_button'; -import { getAlertingCapabilities } from '../../../alerting/get_alerting_capabilities'; -import { BetaBadge } from '../../../shared/beta_badge'; -import { TechnicalPreviewBadge } from '../../../shared/technical_preview_badge'; type Tab = NonNullable[0] & { key: @@ -139,17 +140,21 @@ function TemplateWithContext({ export function isMetricsTabHidden({ agentName, runtimeName, + isAwsLambdaEnabled, }: { agentName?: string; runtimeName?: string; + isAwsLambdaEnabled?: boolean; }) { + if (isServerlessAgent(runtimeName)) { + return !isAwsLambdaEnabled; + } return ( !agentName || isRumAgentName(agentName) || isJavaAgentName(agentName) || isMobileAgentName(agentName) || - isJRubyAgent(agentName, runtimeName) || - isServerlessAgent(runtimeName) + isJRubyAgent(agentName, runtimeName) ); } @@ -192,6 +197,11 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { const router = useApmRouter(); + const isAwsLambdaEnabled = core.uiSettings.get( + enableAwsLambdaMetrics, + true + ); + const { path: { serviceName }, query: queryFromUrl, @@ -257,7 +267,14 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { label: i18n.translate('xpack.apm.serviceDetails.metricsTabLabel', { defaultMessage: 'Metrics', }), - hidden: isMetricsTabHidden({ agentName, runtimeName }), + append: isServerlessAgent(runtimeName) ? ( + + ) : undefined, + hidden: isMetricsTabHidden({ + agentName, + runtimeName, + isAwsLambdaEnabled, + }), }, { key: 'nodes', diff --git a/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx index 1ad5227813d54..41ad3acda28a0 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiTitle } from '@elastic/eui'; import React from 'react'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; import { @@ -60,9 +60,22 @@ interface Props { export function MetricsChart({ chart, fetchStatus }: Props) { return ( <> - - {chart.title} - + + + + {chart.title} + + + {chart.description && ( + + + + )} + {allSeries.map((serie) => { - const Series = serie.type === 'area' ? AreaSeries : LineSeries; + const Series = getChartType(serie.type); return ( { - describe('getScreenSizes', () => { - it('return xs when within 0px - 5740x', () => { - expect(getScreenSizes(0)).toEqual({ - isXSmall: true, - isSmall: true, - isMedium: true, - isLarge: true, - isXl: true, - isXXL: true, - isXXXL: false, - }); - expect(getScreenSizes(574)).toEqual({ - isXSmall: true, - isSmall: true, - isMedium: true, - isLarge: true, - isXl: true, - isXXL: true, - isXXXL: false, - }); - }); - it('return s when within 575px - 767px', () => { - expect(getScreenSizes(575)).toEqual({ - isXSmall: false, - isSmall: true, - isMedium: true, - isLarge: true, - isXl: true, - isXXL: true, - isXXXL: false, - }); - expect(getScreenSizes(767)).toEqual({ - isXSmall: false, - isSmall: true, - isMedium: true, - isLarge: true, - isXl: true, - isXXL: true, - isXXXL: false, - }); - }); - it('return m when within 768px - 991', () => { - expect(getScreenSizes(768)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: true, - isLarge: true, - isXl: true, - isXXL: true, - isXXXL: false, - }); - expect(getScreenSizes(991)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: true, - isLarge: true, - isXl: true, - isXXL: true, - isXXXL: false, - }); - }); - it('return l when within 992px - 1199px', () => { - expect(getScreenSizes(992)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: false, - isLarge: true, - isXl: true, - isXXL: true, - isXXXL: false, - }); - expect(getScreenSizes(1199)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: false, - isLarge: true, - isXl: true, - isXXL: true, - isXXXL: false, - }); - }); - it('return xl when within 1200px - 1599px', () => { - expect(getScreenSizes(1200)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: false, - isLarge: false, - isXl: true, - isXXL: true, - isXXXL: false, - }); - expect(getScreenSizes(1599)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: false, - isLarge: false, - isXl: true, - isXXL: true, - isXXXL: false, - }); - }); - it('return xxl when within 1600px - 1999px', () => { - expect(getScreenSizes(1600)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: false, - isLarge: false, - isXl: false, - isXXL: true, - isXXXL: false, - }); - expect(getScreenSizes(1999)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: false, - isLarge: false, - isXl: false, - isXXL: true, - isXXXL: false, - }); - }); - it('return xxxl when greater than or equals to 2000px', () => { - expect(getScreenSizes(2000)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: false, - isLarge: false, - isXl: false, - isXXL: false, - isXXXL: true, - }); - expect(getScreenSizes(3000)).toEqual({ - isXSmall: false, - isSmall: false, - isMedium: false, - isLarge: false, - isXl: false, - isXXL: false, - isXXXL: true, - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/hooks/use_breakpoints.test.tsx b/x-pack/plugins/apm/public/hooks/use_breakpoints.test.tsx new file mode 100644 index 0000000000000..7af7f258447ed --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_breakpoints.test.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { EuiProvider } from '@elastic/eui'; +import { useBreakpoints } from './use_breakpoints'; + +const wrapper: FC = ({ children }) => ( + + {children} + +); + +describe('useBreakpoints', () => { + test('xs breakpoint', () => { + window.innerWidth = 0; + const { result } = renderHook(() => useBreakpoints(), { wrapper }); + expect(result.current).toEqual({ + isXSmall: true, + isSmall: true, + isMedium: true, + isLarge: true, + isXl: true, + isXXL: true, + isXXXL: false, + }); + }); + + test('s breakpoint', () => { + window.innerWidth = 575; + const { result } = renderHook(() => useBreakpoints(), { wrapper }); + expect(result.current).toEqual({ + isXSmall: false, + isSmall: true, + isMedium: true, + isLarge: true, + isXl: true, + isXXL: true, + isXXXL: false, + }); + }); + + test('m breakpoint', () => { + window.innerWidth = 768; + const { result } = renderHook(() => useBreakpoints(), { wrapper }); + expect(result.current).toEqual({ + isXSmall: false, + isSmall: false, + isMedium: true, + isLarge: true, + isXl: true, + isXXL: true, + isXXXL: false, + }); + }); + + test('l breakpoint', () => { + window.innerWidth = 992; + const { result } = renderHook(() => useBreakpoints(), { wrapper }); + expect(result.current).toEqual({ + isXSmall: false, + isSmall: false, + isMedium: false, + isLarge: true, + isXl: true, + isXXL: true, + isXXXL: false, + }); + }); + + test('xl breakpoint', () => { + window.innerWidth = 1200; + const { result } = renderHook(() => useBreakpoints(), { wrapper }); + expect(result.current).toEqual({ + isXSmall: false, + isSmall: false, + isMedium: false, + isLarge: false, + isXl: true, + isXXL: true, + isXXXL: false, + }); + }); + + test('xxl breakpoint', () => { + window.innerWidth = 1600; + const { result } = renderHook(() => useBreakpoints(), { wrapper }); + expect(result.current).toEqual({ + isXSmall: false, + isSmall: false, + isMedium: false, + isLarge: false, + isXl: false, + isXXL: true, + isXXXL: false, + }); + }); + + test('xxxl breakpoint', () => { + window.innerWidth = 2000; + const { result } = renderHook(() => useBreakpoints(), { wrapper }); + expect(result.current).toEqual({ + isXSmall: false, + isSmall: false, + isMedium: false, + isLarge: false, + isXl: false, + isXXL: false, + isXXXL: true, + }); + }); +}); diff --git a/x-pack/plugins/apm/public/hooks/use_breakpoints.ts b/x-pack/plugins/apm/public/hooks/use_breakpoints.ts index f87ddb929b66a..9ec8b20bb472d 100644 --- a/x-pack/plugins/apm/public/hooks/use_breakpoints.ts +++ b/x-pack/plugins/apm/public/hooks/use_breakpoints.ts @@ -5,42 +5,23 @@ * 2.0. */ -import { useState } from 'react'; -import useWindowSize from 'react-use/lib/useWindowSize'; -import useDebounce from 'react-use/lib/useDebounce'; import { - getBreakpoint, - isWithinMaxBreakpoint, - isWithinMinBreakpoint, + useIsWithinMaxBreakpoint, + useIsWithinMinBreakpoint, } from '@elastic/eui'; -export type Breakpoints = ReturnType; - -export function getScreenSizes(windowWidth: number) { - return { - isXSmall: isWithinMaxBreakpoint(windowWidth, 'xs'), - isSmall: isWithinMaxBreakpoint(windowWidth, 's'), - isMedium: isWithinMaxBreakpoint(windowWidth, 'm'), - isLarge: isWithinMaxBreakpoint(windowWidth, 'l'), - isXl: isWithinMaxBreakpoint(windowWidth, 1599), - isXXL: isWithinMaxBreakpoint(windowWidth, 1999), - isXXXL: isWithinMinBreakpoint(windowWidth, 2000), - }; -} +export type Breakpoints = Record; export function useBreakpoints() { - const { width } = useWindowSize(); - const [breakpoint, setBreakpoint] = useState(getBreakpoint(width)); - const [screenSizes, setScreenSizes] = useState(getScreenSizes(width)); - - useDebounce( - () => { - setBreakpoint(getBreakpoint(width)); - setScreenSizes(getScreenSizes(width)); - }, - 50, - [width] - ); + const screenSizes = { + isXSmall: useIsWithinMaxBreakpoint('xs'), + isSmall: useIsWithinMaxBreakpoint('s'), + isMedium: useIsWithinMaxBreakpoint('m'), + isLarge: useIsWithinMaxBreakpoint('l'), + isXl: useIsWithinMaxBreakpoint('xl'), + isXXL: useIsWithinMaxBreakpoint('xxl'), + isXXXL: useIsWithinMinBreakpoint('xxxl'), + }; - return { ...screenSizes, breakpoint, width }; + return screenSizes; } diff --git a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index 624c3c626fe97..8c55178d7bbed 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -32,7 +32,7 @@ export function useServiceMetricChartsFetcher({ } = useApmParams('/services/{serviceName}'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { agentName, serviceName } = useApmServiceContext(); + const { agentName, serviceName, runtimeName } = useApmServiceContext(); const { data = INITIAL_DATA, @@ -53,13 +53,23 @@ export function useServiceMetricChartsFetcher({ start, end, agentName, + serviceRuntimeName: runtimeName, }, }, } ); } }, - [environment, kuery, serviceName, start, end, agentName, serviceNodeName] + [ + environment, + kuery, + serviceName, + start, + end, + agentName, + serviceNodeName, + runtimeName, + ] ); return { diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 7c9050bd3804e..0ae44172f7ebb 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -29,6 +29,7 @@ import type { PluginSetupContract as AlertingPluginPublicSetup, PluginStartContract as AlertingPluginPublicStart, } from '@kbn/alerting-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { FeaturesPluginSetup } from '@kbn/features-plugin/public'; import type { FleetStart } from '@kbn/fleet-plugin/public'; import type { LicensingPluginSetup } from '@kbn/licensing-plugin/public'; @@ -96,6 +97,7 @@ export interface ApmPluginStartDeps { infra?: InfraClientStartExports; dataViews: DataViewsPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + storage: IStorageWrapper; } const servicesTitle = i18n.translate('xpack.apm.navigation.servicesTitle', { diff --git a/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.ts index c34948d9f797e..266f11f0eff3f 100644 --- a/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.ts @@ -12,12 +12,17 @@ export function getBucketSizeForAggregatedTransactions({ end, numBuckets = 50, searchAggregatedTransactions, + searchAggregatedServiceMetrics, }: { start: number; end: number; numBuckets?: number; searchAggregatedTransactions?: boolean; + searchAggregatedServiceMetrics?: boolean; }) { - const minBucketSize = searchAggregatedTransactions ? 60 : undefined; + const minBucketSize = + searchAggregatedTransactions || searchAggregatedServiceMetrics + ? 60 + : undefined; return getBucketSize({ start, end, numBuckets, minBucketSize }); } diff --git a/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts b/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts new file mode 100644 index 0000000000000..459eddfbdcc1f --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.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 { APMEventClient } from './create_es_client/create_apm_event_client'; +import { getSearchAggregatedTransactions } from './transactions'; +import { getSearchAggregatedServiceMetrics } from './service_metrics'; +import { APMConfig } from '../..'; + +export async function getServiceInventorySearchSource({ + config, + serviceMetricsEnabled, + apmEventClient, + start, + end, + kuery, +}: { + serviceMetricsEnabled: boolean; + config: APMConfig; + apmEventClient: APMEventClient; + start: number; + end: number; + kuery: string; +}): Promise<{ + searchAggregatedTransactions: boolean; + searchAggregatedServiceMetrics: boolean; +}> { + const commonProps = { + apmEventClient, + kuery, + start, + end, + }; + const [searchAggregatedTransactions, searchAggregatedServiceMetrics] = + await Promise.all([ + getSearchAggregatedTransactions({ ...commonProps, config }), + getSearchAggregatedServiceMetrics({ + ...commonProps, + serviceMetricsEnabled, + }), + ]); + + return { + searchAggregatedTransactions, + searchAggregatedServiceMetrics, + }; +} diff --git a/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts b/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts new file mode 100644 index 0000000000000..175267bdf5c2b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.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 { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { METRICSET_NAME } from '../../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../create_es_client/create_apm_event_client'; + +export async function getSearchAggregatedServiceMetrics({ + serviceMetricsEnabled, + start, + end, + apmEventClient, + kuery, +}: { + serviceMetricsEnabled: boolean; + start?: number; + end?: number; + apmEventClient: APMEventClient; + kuery: string; +}): Promise { + if (serviceMetricsEnabled) { + return getHasAggregatedServicesMetrics({ + start, + end, + apmEventClient, + kuery, + }); + } + + return false; +} + +export async function getHasAggregatedServicesMetrics({ + start, + end, + apmEventClient, + kuery, +}: { + start?: number; + end?: number; + apmEventClient: APMEventClient; + kuery: string; +}) { + const response = await apmEventClient.search( + 'get_has_aggregated_service_metrics', + { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + size: 1, + query: { + bool: { + filter: [ + ...getDocumentTypeFilterForServiceMetrics(), + ...(start && end ? rangeQuery(start, end) : []), + ...kqlQuery(kuery), + ], + }, + }, + }, + terminate_after: 1, + } + ); + + return response.hits.total.value > 0; +} + +export function getDocumentTypeFilterForServiceMetrics() { + return [ + { + term: { + [METRICSET_NAME]: 'service', + }, + }, + ]; +} diff --git a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.test.ts b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.test.ts new file mode 100644 index 0000000000000..0905f39f183b4 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.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 { calculateFailedTransactionRateFromServiceMetrics } from './transaction_error_rate'; + +describe('calculateFailedTransactionRateFromServiceMetrics', () => { + it('should return 0 when all params are null', () => { + expect( + calculateFailedTransactionRateFromServiceMetrics({ + failedTransactions: null, + successfulTransactions: null, + }) + ).toBe(0); + }); + + it('should return 9 when failedTransactions:null', () => { + expect( + calculateFailedTransactionRateFromServiceMetrics({ + failedTransactions: null, + successfulTransactions: 2, + }) + ).toBe(0); + }); + + it('should return 0 when failedTransactions:0', () => { + expect( + calculateFailedTransactionRateFromServiceMetrics({ + failedTransactions: 0, + successfulTransactions: null, + }) + ).toBe(0); + }); + + it('should return 1 when failedTransactions:10 and successfulTransactions:0', () => { + expect( + calculateFailedTransactionRateFromServiceMetrics({ + failedTransactions: 10, + successfulTransactions: 0, + }) + ).toBe(1); + }); + it('should return 0,5 when failedTransactions:10 and successfulTransactions:10', () => { + expect( + calculateFailedTransactionRateFromServiceMetrics({ + failedTransactions: 10, + successfulTransactions: 10, + }) + ).toBe(0.5); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts index 5631633b41b72..665784ce53454 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts @@ -9,15 +9,18 @@ import type { AggregationOptionsByType, AggregationResultOf, } from '@kbn/es-types'; +import { isNull } from 'lodash'; import { EVENT_OUTCOME } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; -export const getOutcomeAggregation = () => ({ - terms: { - field: EVENT_OUTCOME, - include: [EventOutcome.failure, EventOutcome.success], - }, -}); +export const getOutcomeAggregation = () => { + return { + terms: { + field: EVENT_OUTCOME, + include: [EventOutcome.failure, EventOutcome.success], + }, + }; +}; type OutcomeAggregation = ReturnType; @@ -48,6 +51,21 @@ export function calculateFailedTransactionRate( return failedTransactions / (successfulTransactions + failedTransactions); } +export function calculateFailedTransactionRateFromServiceMetrics({ + failedTransactions, + successfulTransactions, +}: { + failedTransactions: number | null; + successfulTransactions: number | null; +}) { + if (isNull(failedTransactions) || failedTransactions === 0) { + return 0; + } + + successfulTransactions = successfulTransactions ?? 0; + return failedTransactions / (successfulTransactions + failedTransactions); +} + export function getFailedTransactionRateTimeSeries( buckets: AggregationResultOf< { diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts index 426132b39cc09..5a813ced8e8d5 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts @@ -13,7 +13,6 @@ import { SERVICE_NODE_NAME, } from '../../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { getVizColorForIndex } from '../../../../../common/viz_colors'; import { getMetricsDateHistogramParams } from '../../../../lib/helpers/metrics'; import { Setup } from '../../../../lib/helpers/setup_request'; import { @@ -92,7 +91,14 @@ export async function getActiveInstances({ defaultMessage: 'Active instances', }), key: 'active_instances', - yUnit: 'number', + yUnit: 'integer', + description: i18n.translate( + 'xpack.apm.agentMetrics.serverless.activeInstances.description', + { + defaultMessage: + 'This chart shows the number of active instances of your serverless function over time. Multiple active instances may be a result of provisioned concurrency for your function or an increase in concurrent load that scales your function on-demand. An increase in active instance can be an indicator for an increase in concurrent invocations.', + } + ), series: [ { title: i18n.translate( @@ -100,8 +106,8 @@ export async function getActiveInstances({ { defaultMessage: 'Active instances' } ), key: 'active_instances', - type: 'linemark', - color: getVizColorForIndex(0, theme), + type: 'bar', + color: theme.euiColorVis1, overallValue: aggregations?.activeInstances.value ?? 0, data: aggregations?.timeseriesData.buckets.map((timeseriesBucket) => ({ diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts index fc94a59da9a22..d884aa8dce446 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { termQuery } from '@kbn/observability-plugin/server'; +import { euiLightVars as theme } from '@kbn/ui-theme'; import { FAAS_COLDSTART, METRICSET_NAME, @@ -20,13 +21,14 @@ const chartBase: ChartBase = { defaultMessage: 'Cold start', }), key: 'cold_start_count', - type: 'linemark', - yUnit: 'number', + type: 'bar', + yUnit: 'integer', series: { coldStart: { title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStart', { defaultMessage: 'Cold start', }), + color: theme.euiColorVis5, }, }, }; diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts index 84fac7f99303e..e66d855d1be99 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { euiLightVars as theme } from '@kbn/ui-theme'; import { FAAS_COLDSTART_DURATION } from '../../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../../../lib/helpers/setup_request'; import { fetchAndTransformMetrics } from '../../fetch_and_transform_metrics'; @@ -24,8 +25,16 @@ const chartBase: ChartBase = { 'xpack.apm.agentMetrics.serverless.coldStartDuration', { defaultMessage: 'Cold start duration' } ), + color: theme.euiColorVis5, }, }, + description: i18n.translate( + 'xpack.apm.agentMetrics.serverless.coldStartDuration.description', + { + defaultMessage: + 'Cold start duration shows the execution duration of the serverless runtime for requests that experience cold starts.', + } + ), }; export function getColdStartDuration({ diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts index da57498c8af02..b38e0665a77f2 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts @@ -21,27 +21,9 @@ import { } from '../../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../../common/utils/environment_query'; import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; -import { getVizColorForIndex } from '../../../../../common/viz_colors'; import { getMetricsDateHistogramParams } from '../../../../lib/helpers/metrics'; import { Setup } from '../../../../lib/helpers/setup_request'; import { GenericMetricsChart } from '../../fetch_and_transform_metrics'; -import { ChartBase } from '../../types'; - -const chartBase: ChartBase = { - title: i18n.translate('xpack.apm.agentMetrics.serverless.computeUsage', { - defaultMessage: 'Compute usage', - }), - key: 'compute_usage', - type: 'linemark', - yUnit: 'number', - series: { - computeUsage: { - title: i18n.translate('xpack.apm.agentMetrics.serverless.computeUsage', { - defaultMessage: 'Compute usage', - }), - }, - }, -}; /** * To calculate the compute usage we need to multiply the "system.memory.total" by "faas.billed_duration". @@ -126,9 +108,18 @@ export async function getComputeUsage({ const timeseriesData = aggregations?.timeseriesData; return { - title: chartBase.title, - key: chartBase.key, - yUnit: chartBase.yUnit, + title: i18n.translate('xpack.apm.agentMetrics.serverless.computeUsage', { + defaultMessage: 'Compute usage', + }), + key: 'compute_usage', + yUnit: 'number', + description: i18n.translate( + 'xpack.apm.agentMetrics.serverless.computeUsage.description', + { + defaultMessage: + "Compute usage (in GB-seconds) is the execution time multiplied by the available memory size of your function's instances. The compute usage is a direct indicator for the costs of your serverless function.", + } + ), series: !timeseriesData || timeseriesData.buckets.length === 0 ? [] @@ -139,12 +130,12 @@ export async function getComputeUsage({ { defaultMessage: 'Compute usage' } ), key: 'compute_usage', - type: 'linemark', + type: 'bar', overallValue: calculateComputeUsageGBSeconds({ faasBilledDuration: aggregations?.avgFaasBilledDuration.value, totalMemory: aggregations?.avgTotalMemory.value, }), - color: getVizColorForIndex(0, theme), + color: theme.euiColorVis0, data: timeseriesData.buckets.map((bucket) => { const computeUsage = calculateComputeUsageGBSeconds({ faasBilledDuration: bucket.avgFaasBilledDuration.value, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts index a34165dc95f41..9200e9d07e1ce 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts @@ -51,9 +51,9 @@ export function getServerlessAgentMetricCharts({ ...options, searchAggregatedTransactions, }), + getMemoryChartData(options), getColdStartDuration(options), getColdStartCount(options), - getMemoryChartData(options), getComputeUsage(options), getActiveInstances({ ...options, searchAggregatedTransactions }), ]); diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts index 99a09c86e954f..0db9385615d2a 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts @@ -32,6 +32,13 @@ const chartBase: ChartBase = { type: 'linemark', yUnit: 'time', series: {}, + description: i18n.translate( + 'xpack.apm.agentMetrics.serverless.avgDuration.description', + { + defaultMessage: + 'Transaction duration is the time spent processing and responding to a request. If the request is queued it will not be contribute to the transaction duration but will contribute the overall billed duration', + } + ), }; async function getServerlessLantecySeries({ diff --git a/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts index 9bc5fe20a72c8..b8faa6b140a4b 100644 --- a/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts @@ -21,6 +21,7 @@ import { serviceNodeNameQuery, } from '../../../common/utils/environment_query'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; +import { ChartType, Coordinate, YUnit } from '../../../typings/timeseries'; type MetricsAggregationMap = Unionize<{ min: AggregationOptionsByType['min']; @@ -42,9 +43,22 @@ export type GenericMetricsRequest = APMEventESSearchRequest & { }; }; -export type GenericMetricsChart = Awaited< - ReturnType ->; +export type GenericMetricsChart = Awaited; + +export interface FetchAndTransformMetrics { + title: string; + key: string; + yUnit: YUnit; + series: Array<{ + title: string; + key: string; + type: ChartType; + color: string; + overallValue: number; + data: Coordinate[]; + }>; + description?: string; +} export async function fetchAndTransformMetrics({ environment, @@ -70,7 +84,7 @@ export async function fetchAndTransformMetrics({ aggs: T; additionalFilters?: QueryDslQueryContainer[]; operationName: string; -}) { +}): Promise { const { apmEventClient, config } = setup; const params: GenericMetricsRequest = { @@ -115,6 +129,7 @@ export async function fetchAndTransformMetrics({ title: chartBase.title, key: chartBase.key, yUnit: chartBase.yUnit, + description: chartBase.description, series: hits.total.value === 0 ? [] diff --git a/x-pack/plugins/apm/server/routes/metrics/route.ts b/x-pack/plugins/apm/server/routes/metrics/route.ts index f8b20ae4e0299..5d6b4f245c568 100644 --- a/x-pack/plugins/apm/server/routes/metrics/route.ts +++ b/x-pack/plugins/apm/server/routes/metrics/route.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; +import { FetchAndTransformMetrics } from './fetch_and_transform_metrics'; import { getMetricsChartDataByAgent } from './get_metrics_chart_data_by_agent'; const metricsChartsRoute = createApmServerRoute({ @@ -34,19 +35,7 @@ const metricsChartsRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ - charts: Array<{ - title: string; - key: string; - yUnit: import('./../../../typings/timeseries').YUnit; - series: Array<{ - title: string; - key: string; - type: import('./../../../typings/timeseries').ChartType; - color: string; - overallValue: number; - data: Array<{ x: number; y: number | null }>; - }>; - }>; + charts: FetchAndTransformMetrics[]; }> => { const { params } = resources; const setup = await setupRequest(resources); diff --git a/x-pack/plugins/apm/server/routes/metrics/types.ts b/x-pack/plugins/apm/server/routes/metrics/types.ts index 92ca587e3bdff..0cbf1cfce0916 100644 --- a/x-pack/plugins/apm/server/routes/metrics/types.ts +++ b/x-pack/plugins/apm/server/routes/metrics/types.ts @@ -18,4 +18,5 @@ export interface ChartBase { color?: string; }; }; + description?: string; } diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts new file mode 100644 index 0000000000000..d3364062eb2fb --- /dev/null +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.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 { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + AGENT_NAME, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_TYPE, + TRANSACTION_DURATION_SUMMARY, + TRANSACTION_FAILURE_COUNT, + TRANSACTION_SUCCESS_COUNT, +} from '../../../../common/elasticsearch_fieldnames'; +import { + TRANSACTION_PAGE_LOAD, + TRANSACTION_REQUEST, +} from '../../../../common/transaction_types'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; +import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput'; +import { calculateFailedTransactionRateFromServiceMetrics } from '../../../lib/helpers/transaction_error_rate'; +import { ServicesItemsSetup } from './get_services_items'; +import { serviceGroupQuery } from '../../../lib/service_group_query'; +import { ServiceGroup } from '../../../../common/service_groups'; +import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { getDocumentTypeFilterForServiceMetrics } from '../../../lib/helpers/service_metrics'; +interface AggregationParams { + environment: string; + kuery: string; + setup: ServicesItemsSetup; + maxNumServices: number; + start: number; + end: number; + serviceGroup: ServiceGroup | null; + randomSampler: RandomSampler; +} + +export async function getServiceAggregatedTransactionStats({ + environment, + kuery, + setup, + maxNumServices, + start, + end, + serviceGroup, + randomSampler, +}: AggregationParams) { + const { apmEventClient } = setup; + + const response = await apmEventClient.search( + 'get_service_aggregated_transaction_stats', + { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + ...getDocumentTypeFilterForServiceMetrics(), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...serviceGroupQuery(serviceGroup), + ], + }, + }, + aggs: { + sample: { + random_sampler: randomSampler, + aggs: { + services: { + terms: { + field: SERVICE_NAME, + size: maxNumServices, + }, + aggs: { + transactionType: { + terms: { + field: TRANSACTION_TYPE, + }, + aggs: { + avg_duration: { + avg: { + field: TRANSACTION_DURATION_SUMMARY, + }, + }, + total_doc: { + value_count: { + field: TRANSACTION_DURATION_SUMMARY, + }, + }, + failure_count: { + sum: { + field: TRANSACTION_FAILURE_COUNT, + }, + }, + success_count: { + sum: { + field: TRANSACTION_SUCCESS_COUNT, + }, + }, + environments: { + terms: { + field: SERVICE_ENVIRONMENT, + }, + }, + sample: { + top_metrics: { + metrics: [{ field: AGENT_NAME } as const], + sort: { + '@timestamp': 'desc' as const, + }, + }, + }, + bucket_sort: { + bucket_sort: { + sort: [ + { + total_doc: { + order: 'desc', + }, + }, + ], + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + ); + + return ( + response.aggregations?.sample.services.buckets.map((bucket) => { + const topTransactionTypeBucket = + bucket.transactionType.buckets.find( + ({ key }) => + key === TRANSACTION_REQUEST || key === TRANSACTION_PAGE_LOAD + ) ?? bucket.transactionType.buckets[0]; + + return { + serviceName: bucket.key as string, + transactionType: topTransactionTypeBucket.key as string, + environments: topTransactionTypeBucket.environments.buckets.map( + (environmentBucket) => environmentBucket.key as string + ), + agentName: topTransactionTypeBucket.sample.top[0].metrics[ + AGENT_NAME + ] as AgentName, + latency: topTransactionTypeBucket.avg_duration.value, + transactionErrorRate: calculateFailedTransactionRateFromServiceMetrics({ + failedTransactions: topTransactionTypeBucket.failure_count.value, + successfulTransactions: topTransactionTypeBucket.success_count.value, + }), + throughput: calculateThroughputWithRange({ + start, + end, + value: topTransactionTypeBucket.total_doc.value, + }), + }; + }) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts index c8d83df9a700e..7ee41a2bea0e0 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts @@ -11,6 +11,7 @@ import { Setup } from '../../../lib/helpers/setup_request'; import { getHealthStatuses } from './get_health_statuses'; import { getServicesFromErrorAndMetricDocuments } from './get_services_from_error_and_metric_documents'; import { getServiceTransactionStats } from './get_service_transaction_stats'; +import { getServiceAggregatedTransactionStats } from './get_service_aggregated_transaction_stats'; import { mergeServiceStats } from './merge_service_stats'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; @@ -24,6 +25,7 @@ export async function getServicesItems({ kuery, setup, searchAggregatedTransactions, + searchAggregatedServiceMetrics, logger, start, end, @@ -34,6 +36,7 @@ export async function getServicesItems({ kuery: string; setup: ServicesItemsSetup; searchAggregatedTransactions: boolean; + searchAggregatedServiceMetrics: boolean; logger: Logger; start: number; end: number; @@ -46,6 +49,7 @@ export async function getServicesItems({ kuery, setup, searchAggregatedTransactions, + searchAggregatedServiceMetrics, maxNumServices: MAX_NUMBER_OF_SERVICES, start, end, @@ -58,7 +62,9 @@ export async function getServicesItems({ servicesFromErrorAndMetricDocuments, healthStatuses, ] = await Promise.all([ - getServiceTransactionStats(params), + searchAggregatedServiceMetrics + ? getServiceAggregatedTransactionStats(params) + : getServiceTransactionStats(params), getServicesFromErrorAndMetricDocuments(params), getHealthStatuses(params).catch((err) => { logger.error(err); diff --git a/x-pack/plugins/apm/server/routes/services/get_services/index.ts b/x-pack/plugins/apm/server/routes/services/get_services/index.ts index 954da17c7249d..83932f630357e 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/index.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/index.ts @@ -17,6 +17,7 @@ export async function getServices({ kuery, setup, searchAggregatedTransactions, + searchAggregatedServiceMetrics, logger, start, end, @@ -27,6 +28,7 @@ export async function getServices({ kuery: string; setup: Setup; searchAggregatedTransactions: boolean; + searchAggregatedServiceMetrics: boolean; logger: Logger; start: number; end: number; @@ -39,6 +41,7 @@ export async function getServices({ kuery, setup, searchAggregatedTransactions, + searchAggregatedServiceMetrics, logger, start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts new file mode 100644 index 0000000000000..e1e1c84598eff --- /dev/null +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts @@ -0,0 +1,244 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { keyBy } from 'lodash'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; +import { + SERVICE_NAME, + TRANSACTION_TYPE, + TRANSACTION_DURATION_SUMMARY, + TRANSACTION_FAILURE_COUNT, + TRANSACTION_SUCCESS_COUNT, +} from '../../../../common/elasticsearch_fieldnames'; +import { withApmSpan } from '../../../utils/with_apm_span'; +import { + TRANSACTION_PAGE_LOAD, + TRANSACTION_REQUEST, +} from '../../../../common/transaction_types'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput'; +import { getBucketSizeForAggregatedTransactions } from '../../../lib/helpers/get_bucket_size_for_aggregated_transactions'; +import { Setup } from '../../../lib/helpers/setup_request'; +import { calculateFailedTransactionRateFromServiceMetrics } from '../../../lib/helpers/transaction_error_rate'; +import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { getDocumentTypeFilterForServiceMetrics } from '../../../lib/helpers/service_metrics'; + +export async function getServiceAggregatedTransactionDetailedStats({ + serviceNames, + environment, + kuery, + setup, + searchAggregatedServiceMetrics, + offset, + start, + end, + randomSampler, +}: { + serviceNames: string[]; + environment: string; + kuery: string; + setup: Setup; + searchAggregatedServiceMetrics: boolean; + offset?: string; + start: number; + end: number; + randomSampler: RandomSampler; +}) { + const { apmEventClient } = setup; + const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + + const metrics = { + avg_duration: { + avg: { + field: TRANSACTION_DURATION_SUMMARY, + }, + }, + total_doc: { + value_count: { + field: TRANSACTION_DURATION_SUMMARY, + }, + }, + failure_count: { + sum: { + field: TRANSACTION_FAILURE_COUNT, + }, + }, + success_count: { + sum: { + field: TRANSACTION_SUCCESS_COUNT, + }, + }, + }; + + const response = await apmEventClient.search( + 'get_service_aggregated_transaction_detail_stats', + { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { terms: { [SERVICE_NAME]: serviceNames } }, + ...getDocumentTypeFilterForServiceMetrics(), + ...rangeQuery(startWithOffset, endWithOffset), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + sample: { + random_sampler: randomSampler, + aggs: { + services: { + terms: { + field: SERVICE_NAME, + size: serviceNames.length, + }, + aggs: { + transactionType: { + terms: { + field: TRANSACTION_TYPE, + }, + aggs: { + ...metrics, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: + getBucketSizeForAggregatedTransactions({ + start: startWithOffset, + end: endWithOffset, + numBuckets: 20, + searchAggregatedServiceMetrics, + }).intervalString, + min_doc_count: 0, + extended_bounds: { + min: startWithOffset, + max: endWithOffset, + }, + }, + aggs: metrics, + }, + bucket_sort: { + bucket_sort: { + sort: [ + { + total_doc: { + order: 'desc', + }, + }, + ], + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + ); + + return keyBy( + response.aggregations?.sample.services.buckets.map((bucket) => { + const topTransactionTypeBucket = + bucket.transactionType.buckets.find( + ({ key }) => + key === TRANSACTION_REQUEST || key === TRANSACTION_PAGE_LOAD + ) ?? bucket.transactionType.buckets[0]; + + return { + serviceName: bucket.key as string, + latency: topTransactionTypeBucket.timeseries.buckets.map( + (dateBucket) => ({ + x: dateBucket.key + offsetInMs, + y: dateBucket.avg_duration.value, + }) + ), + transactionErrorRate: topTransactionTypeBucket.timeseries.buckets.map( + (dateBucket) => ({ + x: dateBucket.key + offsetInMs, + y: calculateFailedTransactionRateFromServiceMetrics({ + failedTransactions: dateBucket.failure_count.value, + successfulTransactions: dateBucket.success_count.value, + }), + }) + ), + throughput: topTransactionTypeBucket.timeseries.buckets.map( + (dateBucket) => ({ + x: dateBucket.key + offsetInMs, + y: calculateThroughputWithRange({ + start, + end, + value: dateBucket.total_doc.value, + }), + }) + ), + }; + }) ?? [], + 'serviceName' + ); +} + +export async function getServiceAggregatedDetailedStatsPeriods({ + serviceNames, + environment, + kuery, + setup, + searchAggregatedServiceMetrics, + offset, + start, + end, + randomSampler, +}: { + serviceNames: string[]; + environment: string; + kuery: string; + setup: Setup; + searchAggregatedServiceMetrics: boolean; + offset?: string; + start: number; + end: number; + randomSampler: RandomSampler; +}) { + return withApmSpan('get_service_aggregated_detailed_stats', async () => { + const commonProps = { + serviceNames, + environment, + kuery, + setup, + searchAggregatedServiceMetrics, + start, + end, + randomSampler, + }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + getServiceAggregatedTransactionDetailedStats(commonProps), + offset + ? getServiceAggregatedTransactionDetailedStats({ + ...commonProps, + offset, + }) + : Promise.resolve({}), + ]); + + return { currentPeriod, previousPeriod }; + }); +} diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts index 600d3c71f7e08..a19d7e5984cf8 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts @@ -11,6 +11,7 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, @@ -31,7 +32,7 @@ import { } from '../../../lib/helpers/transaction_error_rate'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; -export async function getServiceTransactionDetailedStatistics({ +export async function getServiceTransactionDetailedStats({ serviceNames, environment, kuery, @@ -175,3 +176,50 @@ export async function getServiceTransactionDetailedStatistics({ 'serviceName' ); } + +export async function getServiceDetailedStatsPeriods({ + serviceNames, + environment, + kuery, + setup, + searchAggregatedTransactions, + offset, + start, + end, + randomSampler, +}: { + serviceNames: string[]; + environment: string; + kuery: string; + setup: Setup; + searchAggregatedTransactions: boolean; + offset?: string; + start: number; + end: number; + randomSampler: RandomSampler; +}) { + return withApmSpan('get_service_detailed_statistics', async () => { + const commonProps = { + serviceNames, + environment, + kuery, + setup, + searchAggregatedTransactions, + start, + end, + randomSampler, + }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + getServiceTransactionDetailedStats(commonProps), + offset + ? getServiceTransactionDetailedStats({ + ...commonProps, + offset, + }) + : Promise.resolve({}), + ]); + + return { currentPeriod, previousPeriod }; + }); +} diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts index d75bf9d970c9c..b142b3484d559 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../../lib/helpers/setup_request'; -import { getServiceTransactionDetailedStatistics } from './get_service_transaction_detailed_statistics'; +import { getServiceDetailedStatsPeriods } from './get_service_transaction_detailed_statistics'; +import { getServiceAggregatedDetailedStatsPeriods } from './get_service_aggregated_transaction_detailed_statistics'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; export async function getServicesDetailedStatistics({ @@ -16,6 +16,7 @@ export async function getServicesDetailedStatistics({ kuery, setup, searchAggregatedTransactions, + searchAggregatedServiceMetrics, offset, start, end, @@ -26,30 +27,29 @@ export async function getServicesDetailedStatistics({ kuery: string; setup: Setup; searchAggregatedTransactions: boolean; + searchAggregatedServiceMetrics: boolean; offset?: string; start: number; end: number; randomSampler: RandomSampler; }) { - return withApmSpan('get_service_detailed_statistics', async () => { - const commonProps = { - serviceNames, - environment, - kuery, - setup, - searchAggregatedTransactions, - start, - end, - randomSampler, - }; - - const [currentPeriod, previousPeriod] = await Promise.all([ - getServiceTransactionDetailedStatistics(commonProps), - offset - ? getServiceTransactionDetailedStatistics({ ...commonProps, offset }) - : Promise.resolve({}), - ]); - - return { currentPeriod, previousPeriod }; - }); + const commonProps = { + serviceNames, + environment, + kuery, + setup, + start, + end, + randomSampler, + offset, + }; + return searchAggregatedServiceMetrics + ? getServiceAggregatedDetailedStatsPeriods({ + ...commonProps, + searchAggregatedServiceMetrics, + }) + : getServiceDetailedStatsPeriods({ + ...commonProps, + searchAggregatedTransactions, + }); } diff --git a/x-pack/plugins/apm/server/routes/services/queries.test.ts b/x-pack/plugins/apm/server/routes/services/queries.test.ts index d02a188d632a3..ff9f79867c2d7 100644 --- a/x-pack/plugins/apm/server/routes/services/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/services/queries.test.ts @@ -54,6 +54,7 @@ describe('services queries', () => { getServicesItems({ setup, searchAggregatedTransactions: false, + searchAggregatedServiceMetrics: false, logger: {} as any, environment: ENVIRONMENT_ALL.value, kuery: '', diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 4b76e53877d38..6b73d72367d0f 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import { isoToEpochRt, jsonRt, toNumberRt } from '@kbn/io-ts-utils'; +import { enableServiceMetrics } from '@kbn/observability-plugin/common'; import * as t from 'io-ts'; import { uniq, mergeWith } from 'lodash'; import { @@ -19,6 +20,7 @@ import { Annotation } from '@kbn/observability-plugin/common/annotations'; import { apmServiceGroupMaxNumberOfServices } from '@kbn/observability-plugin/common'; import { latencyAggregationTypeRt } from '../../../common/latency_aggregation_types'; import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getServiceInventorySearchSource } from '../../lib/helpers/get_service_inventory_search_source'; import { setupRequest } from '../../lib/helpers/setup_request'; import { getServiceAnnotations } from './annotations'; import { getServices } from './get_services'; @@ -121,6 +123,7 @@ const servicesRoute = createApmServerRoute({ probability, } = params.query; const savedObjectsClient = (await context.core).savedObjects.client; + const coreContext = await resources.context.core; const [setup, serviceGroup, randomSampler] = await Promise.all([ setupRequest(resources), @@ -129,18 +132,28 @@ const servicesRoute = createApmServerRoute({ : Promise.resolve(null), getRandomSampler({ security, request, probability }), ]); - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ - ...setup, - kuery, - start, - end, - }); + + const { apmEventClient, config } = setup; + + const serviceMetricsEnabled = + await coreContext.uiSettings.client.get(enableServiceMetrics); + + const { searchAggregatedTransactions, searchAggregatedServiceMetrics } = + await getServiceInventorySearchSource({ + serviceMetricsEnabled, + config, + apmEventClient, + kuery, + start, + end, + }); return getServices({ environment, kuery, setup, searchAggregatedTransactions, + searchAggregatedServiceMetrics, logger, start, end, @@ -202,6 +215,7 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ request, plugins: { security }, } = resources; + const coreContext = await resources.context.core; const { environment, kuery, offset, start, end, probability } = params.query; @@ -213,12 +227,20 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ getRandomSampler({ security, request, probability }), ]); - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ - ...setup, - start, - end, - kuery, - }); + const { apmEventClient, config } = setup; + + const serviceMetricsEnabled = + await coreContext.uiSettings.client.get(enableServiceMetrics); + + const { searchAggregatedTransactions, searchAggregatedServiceMetrics } = + await getServiceInventorySearchSource({ + serviceMetricsEnabled, + config, + apmEventClient, + kuery, + start, + end, + }); if (!serviceNames.length) { throw Boom.badRequest(`serviceNames cannot be empty`); @@ -229,6 +251,7 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ kuery, setup, searchAggregatedTransactions, + searchAggregatedServiceMetrics, offset, serviceNames, start, diff --git a/x-pack/plugins/apm/typings/timeseries.ts b/x-pack/plugins/apm/typings/timeseries.ts index b675873fc1d3c..2b2af4f3bff06 100644 --- a/x-pack/plugins/apm/typings/timeseries.ts +++ b/x-pack/plugins/apm/typings/timeseries.ts @@ -69,5 +69,5 @@ export interface APMChartSpec< groupId?: string; } -export type ChartType = 'area' | 'linemark'; +export type ChartType = 'area' | 'linemark' | 'bar'; export type YUnit = 'percent' | 'bytes' | 'number' | 'time' | 'integer'; diff --git a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot index 208d358405f19..ea557ae50328f 100644 --- a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot @@ -36,6 +36,7 @@ exports[`Storyshots components/Variables/VarConfig default 1`] = ` aria-controls="accordion-variables" aria-expanded={false} className="euiAccordion__button emotion-euiAccordion__button" + disabled={false} id="generated-id" onClick={[Function]} type="button" diff --git a/x-pack/plugins/canvas/public/components/workpad_filters/__stories__/__snapshots__/filters_group.component.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_filters/__stories__/__snapshots__/filters_group.component.stories.storyshot index cc3140aa50c1e..daf25ece5607a 100644 --- a/x-pack/plugins/canvas/public/components/workpad_filters/__stories__/__snapshots__/filters_group.component.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_filters/__stories__/__snapshots__/filters_group.component.stories.storyshot @@ -41,6 +41,7 @@ exports[`Storyshots components/WorkpadFilters/FiltersGroupComponent default 1`] aria-controls="canvas-filter-group-0" aria-expanded={true} className="euiAccordion__button emotion-euiAccordion__button" + disabled={false} id="generated-id" onClick={[Function]} type="button" @@ -696,6 +697,7 @@ exports[`Storyshots components/WorkpadFilters/FiltersGroupComponent empty group aria-controls="canvas-filter-group-0" aria-expanded={true} className="euiAccordion__button emotion-euiAccordion__button" + disabled={false} id="generated-id" onClick={[Function]} type="button" diff --git a/x-pack/plugins/canvas/public/components/workpad_filters/__stories__/__snapshots__/workpad_filters.component.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_filters/__stories__/__snapshots__/workpad_filters.component.stories.storyshot index 7cc8edb5e69c9..86d0fd0a46c22 100644 --- a/x-pack/plugins/canvas/public/components/workpad_filters/__stories__/__snapshots__/workpad_filters.component.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_filters/__stories__/__snapshots__/workpad_filters.component.stories.storyshot @@ -206,6 +206,7 @@ exports[`Storyshots components/WorkpadFilters/WorkpadFiltersComponent Filters gr aria-controls="canvas-filter-group-0" aria-expanded={true} className="euiAccordion__button emotion-euiAccordion__button" + disabled={false} id="generated-id" onClick={[Function]} type="button" @@ -941,6 +942,7 @@ exports[`Storyshots components/WorkpadFilters/WorkpadFiltersComponent Filters gr aria-controls="canvas-filter-group-0" aria-expanded={true} className="euiAccordion__button emotion-euiAccordion__button" + disabled={false} id="generated-id" onClick={[Function]} type="button" @@ -1676,6 +1678,7 @@ exports[`Storyshots components/WorkpadFilters/WorkpadFiltersComponent Filters gr aria-controls="canvas-filter-group-0" aria-expanded={true} className="euiAccordion__button emotion-euiAccordion__button" + disabled={false} id="generated-id" onClick={[Function]} type="button" @@ -1834,6 +1837,7 @@ exports[`Storyshots components/WorkpadFilters/WorkpadFiltersComponent default 1` aria-controls="canvas-filter-group-0" aria-expanded={true} className="euiAccordion__button emotion-euiAccordion__button" + disabled={false} id="generated-id" onClick={[Function]} type="button" @@ -2194,6 +2198,7 @@ exports[`Storyshots components/WorkpadFilters/WorkpadFiltersComponent default 1` aria-controls="canvas-filter-group-1" aria-expanded={true} className="euiAccordion__button emotion-euiAccordion__button" + disabled={false} id="generated-id" onClick={[Function]} type="button" diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx index c17607918cb4f..6fd1556f7d4e1 100644 --- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx @@ -6,7 +6,6 @@ */ import React from 'react'; -import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; import { AllCases } from '.'; @@ -14,7 +13,6 @@ import { AppMockRenderer, createAppMockRenderer, noCreateCasesPermissions, - TestProviders, } from '../../common/mock'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { casesStatus, connectorsMock, useGetCasesMockState } from '../../containers/mock'; @@ -46,8 +44,7 @@ const useGetActionLicenseMock = useGetActionLicense as jest.Mock; const useGetCurrentUserProfileMock = useGetCurrentUserProfile as jest.Mock; const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock; -// FLAKY: https://github.com/elastic/kibana/issues/139677 -describe.skip('AllCases', () => { +describe('AllCases', () => { const refetchCases = jest.fn(); const setFilters = jest.fn(); const setQueryParams = jest.fn(); @@ -126,58 +123,33 @@ describe.skip('AllCases', () => { ...defaultGetCases, }); - const wrapper = mount( - - - - ); + const result = appMockRender.render(); + + await waitFor(() => { + expect(result.getByTestId('openStatsHeader')).toBeInTheDocument(); + expect(result.getByText('20')).toBeInTheDocument(); + }); + + await waitFor(() => { + expect(result.getByTestId('inProgressStatsHeader')).toBeInTheDocument(); + expect(result.getByText('40')).toBeInTheDocument(); + }); await waitFor(() => { - expect(wrapper.find('[data-test-subj="openStatsHeader"]').exists()).toBeTruthy(); - expect( - wrapper - .find('[data-test-subj="openStatsHeader"] .euiDescriptionList__description') - .first() - .text() - ).toBe('20'); - - expect(wrapper.find('[data-test-subj="inProgressStatsHeader"]').exists()).toBeTruthy(); - expect( - wrapper - .find('[data-test-subj="inProgressStatsHeader"] .euiDescriptionList__description') - .first() - .text() - ).toBe('40'); - - expect(wrapper.find('[data-test-subj="closedStatsHeader"]').exists()).toBeTruthy(); - expect( - wrapper - .find('[data-test-subj="closedStatsHeader"] .euiDescriptionList__description') - .first() - .text() - ).toBe('130'); + expect(result.getByTestId('closedStatsHeader')).toBeInTheDocument(); + expect(result.getByText('130')).toBeInTheDocument(); }); }); it('should render the loading spinner when loading stats', async () => { useGetCasesStatusMock.mockReturnValue({ ...defaultCasesStatus, isLoading: true }); - const wrapper = mount( - - - - ); + const result = appMockRender.render(); await waitFor(() => { - expect( - wrapper.find('[data-test-subj="openStatsHeader-loading-spinner"]').exists() - ).toBeTruthy(); - expect( - wrapper.find('[data-test-subj="inProgressStatsHeader-loading-spinner"]').exists() - ).toBeTruthy(); - expect( - wrapper.find('[data-test-subj="closedStatsHeader-loading-spinner"]').exists() - ).toBeTruthy(); + expect(result.getByTestId('openStatsHeader-loading-spinner')).toBeInTheDocument(); + expect(result.getByTestId('inProgressStatsHeader-loading-spinner')).toBeInTheDocument(); + expect(result.getByTestId('closedStatsHeader-loading-spinner')).toBeInTheDocument(); }); }); @@ -194,16 +166,10 @@ describe.skip('AllCases', () => { }, }); - const wrapper = mount( - - - - ); + const result = appMockRender.render(); await waitFor(() => { - expect( - wrapper.find('[data-test-subj="configure-case-button"]').first().prop('isDisabled') - ).toBeTruthy(); + expect(result.getByTestId('configure-case-button')).toBeDisabled(); }); }); @@ -220,16 +186,10 @@ describe.skip('AllCases', () => { }, }); - const wrapper = mount( - - - - ); + const result = appMockRender.render(); await waitFor(() => { - expect( - wrapper.find('[data-test-subj="configure-case-button"]').first().prop('isDisabled') - ).toBeFalsy(); + expect(result.getByTestId('configure-case-button')).not.toBeDisabled(); }); }); diff --git a/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx b/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx index 8719ef2662954..a06ae9e772c11 100644 --- a/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx @@ -427,6 +427,11 @@ describe('CaseViewPage', () => { }), }, }, + notifications: { + toasts: { + addDanger: () => {}, + }, + }, }, }), })); diff --git a/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx b/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx index 82ed36ef25243..46138fb354604 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx @@ -6,14 +6,22 @@ */ import React from 'react'; -import { configure } from '@testing-library/react'; +import { configure, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import RecentCases, { RecentCasesProps } from '.'; -import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock'; +import { + AppMockRenderer, + createAppMockRenderer, + noCasesCapabilities, + TestProviders, +} from '../../common/mock'; import { useGetCasesMockState } from '../../containers/mock'; import { useCurrentUser } from '../../common/lib/kibana/hooks'; import { useGetCases } from '../../containers/use_get_cases'; +import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; +import { userProfiles } from '../../containers/user_profiles/api.mock'; +jest.mock('../../containers/user_profiles/use_get_current_user_profile'); jest.mock('../../containers/use_get_cases'); jest.mock('../../common/lib/kibana/hooks'); jest.mock('../../common/navigation/hooks'); @@ -27,6 +35,7 @@ const mockData = { ...useGetCasesMockState, }; +const useGetCurrentUserProfileMock = useGetCurrentUserProfile as jest.Mock; const useGetCasesMock = useGetCases as jest.Mock; const useCurrentUserMock = useCurrentUser as jest.Mock; @@ -34,12 +43,17 @@ describe('RecentCases', () => { let appMockRender: AppMockRenderer; beforeEach(() => { jest.clearAllMocks(); + useGetCurrentUserProfileMock.mockReturnValue({ + data: userProfiles[0], + isLoading: false, + }); useGetCasesMock.mockImplementation(() => mockData); - useCurrentUserMock.mockResolvedValue({ + useCurrentUserMock.mockReturnValue({ email: 'elastic@elastic.co', fullName: 'Elastic', username: 'elastic', }); + appMockRender = createAppMockRenderer(); }); @@ -73,12 +87,12 @@ describe('RecentCases', () => { ); expect(useGetCasesMock).toHaveBeenCalledWith({ - filterOptions: { reporters: [] }, + filterOptions: { reporters: [], owner: ['securitySolution'] }, queryParams: { perPage: 2 }, }); }); - it('sets the reporter filters correctly', () => { + it('sets the reporter filters correctly', async () => { const { getByTestId } = appMockRender.render( @@ -86,17 +100,27 @@ describe('RecentCases', () => { ); expect(useGetCasesMock).toHaveBeenCalledWith({ - filterOptions: { reporters: [] }, + filterOptions: { reporters: [], owner: ['securitySolution'] }, queryParams: { perPage: 10 }, }); // apply the filter - const myRecentCasesElement = getByTestId('myRecentlyReported'); - userEvent.click(myRecentCasesElement); + await waitFor(() => { + const myRecentCasesElement = getByTestId('myRecentlyReported'); + userEvent.click(myRecentCasesElement); + }); expect(useGetCasesMock).toHaveBeenLastCalledWith({ filterOptions: { - reporters: [{ email: undefined, full_name: undefined, username: undefined }], + reporters: [ + { + email: 'damaged_raccoon@elastic.co', + full_name: 'Damaged Raccoon', + profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', + username: 'damaged_raccoon', + }, + ], + owner: ['securitySolution'], }, queryParams: { perPage: 10 }, }); @@ -108,8 +132,64 @@ describe('RecentCases', () => { expect(useGetCasesMock).toHaveBeenLastCalledWith({ filterOptions: { reporters: [], + owner: ['securitySolution'], }, queryParams: { perPage: 10 }, }); }); + + it('sets the reporter filters to the user info without the profile uid when it cannot find the current user profile', async () => { + useGetCurrentUserProfileMock.mockReturnValue({ data: undefined, isLoading: false }); + + const { getByTestId } = appMockRender.render( + + + + ); + + expect(useGetCasesMock).toHaveBeenCalledWith({ + filterOptions: { reporters: [], owner: ['securitySolution'] }, + queryParams: { perPage: 10 }, + }); + + // apply the filter + await waitFor(() => { + const myRecentCasesElement = getByTestId('myRecentlyReported'); + userEvent.click(myRecentCasesElement); + }); + + expect(useGetCasesMock).toHaveBeenLastCalledWith({ + filterOptions: { + reporters: [ + { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + ], + owner: ['securitySolution'], + }, + queryParams: { perPage: 10 }, + }); + }); + + it('sets all available solutions correctly', () => { + appMockRender = createAppMockRenderer({ owner: [] }); + /** + * We set securitySolutionCases capability to not have + * any access to cases. This tests that we get the owners + * that have at least read access. + */ + appMockRender.coreStart.application.capabilities = { + ...appMockRender.coreStart.application.capabilities, + securitySolutionCases: noCasesCapabilities(), + }; + + appMockRender.render(); + + expect(useGetCasesMock).toHaveBeenCalledWith({ + filterOptions: { reporters: [], owner: ['cases'] }, + queryParams: { perPage: 2 }, + }); + }); }); diff --git a/x-pack/plugins/cases/public/components/recent_cases/index.tsx b/x-pack/plugins/cases/public/components/recent_cases/index.tsx index c8b7581e53107..c63c98b14a6e9 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/index.tsx @@ -9,21 +9,39 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText, EuiTitle } from import React, { useCallback, useMemo, useState } from 'react'; import { QueryClientProvider } from '@tanstack/react-query'; +import { UserProfile } from '@kbn/user-profile-components'; import * as i18n from './translations'; import { LinkAnchor } from '../links'; import { RecentCasesFilters } from './filters'; import { RecentCasesComp } from './recent_cases'; import { FilterMode as RecentCasesFilterMode } from './types'; -import { useCurrentUser } from '../../common/lib/kibana'; +import { AuthenticatedElasticUser, useCurrentUser } from '../../common/lib/kibana'; import { useAllCasesNavigation } from '../../common/navigation'; import { casesQueryClient } from '../cases_context/query_client'; +import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; +import { User } from '../../../common/api'; export interface RecentCasesProps { maxCasesToShow: number; } -const RecentCases = React.memo(({ maxCasesToShow }: RecentCasesProps) => { +const RecentCases = React.memo((props: RecentCasesProps) => { + return ( + + + + ); +}); + +RecentCases.displayName = 'RecentCases'; + +// eslint-disable-next-line import/no-default-export +export { RecentCases as default }; + +const RecentCasesWithoutQueryProvider = React.memo(({ maxCasesToShow }: RecentCasesProps) => { const currentUser = useCurrentUser(); + const { data: currentUserProfile, isLoading: isLoadingCurrentUserProfile } = + useGetCurrentUserProfile(); const { getAllCasesUrl, navigateToAllCases } = useAllCasesNavigation(); const [recentCasesFilterBy, setRecentCasesFilterBy] = @@ -37,42 +55,36 @@ const RecentCases = React.memo(({ maxCasesToShow }: RecentCasesProps) => { [navigateToAllCases] ); - const recentCasesFilterOptions = useMemo( - () => - recentCasesFilterBy === 'myRecentlyReported' && currentUser != null - ? { - reporters: [ - { - email: currentUser.email, - full_name: currentUser.fullName, - username: currentUser.username, - }, - ], - } - : { reporters: [] }, - [currentUser, recentCasesFilterBy] - ); + const recentCasesFilterOptions = useMemo(() => { + return getReporterFilter({ + currentUser, + isLoadingCurrentUserProfile, + recentCasesFilterBy, + currentUserProfile, + }); + }, [currentUser, currentUserProfile, isLoadingCurrentUserProfile, recentCasesFilterBy]); + + // show the recently reported if we have the current user profile, or if we have the fallback user information + const showMyRecentlyReported = currentUserProfile != null || currentUser != null; return ( - - <> - - - -

{i18n.RECENT_CASES}

-
-
- - - - -
- - + <> + + + +

{i18n.RECENT_CASES}

+
+
+ + + + +
+ @@ -83,11 +95,50 @@ const RecentCases = React.memo(({ maxCasesToShow }: RecentCasesProps) => { -
+ ); }); -RecentCases.displayName = 'RecentCases'; +RecentCasesWithoutQueryProvider.displayName = 'RecentCases'; -// eslint-disable-next-line import/no-default-export -export { RecentCases as default }; +const getReporterFilter = ({ + recentCasesFilterBy, + currentUserProfile, + currentUser, + isLoadingCurrentUserProfile, +}: { + recentCasesFilterBy: RecentCasesFilterMode; + currentUserProfile?: UserProfile; + currentUser: AuthenticatedElasticUser | null; + isLoadingCurrentUserProfile: boolean; +}): { reporters: User[] } => { + const emptyFilter = { reporters: [] }; + if (recentCasesFilterBy !== 'myRecentlyReported') { + return emptyFilter; + } + + if (currentUserProfile != null && !isLoadingCurrentUserProfile) { + return { + reporters: [ + { + email: currentUserProfile.user.email, + full_name: currentUserProfile.user.full_name, + username: currentUserProfile.user.username, + profile_uid: currentUserProfile.uid, + }, + ], + }; + } else if (currentUser != null) { + return { + reporters: [ + { + email: currentUser.email, + full_name: currentUser.fullName, + username: currentUser.username, + }, + ], + }; + } + + return emptyFilter; +}; diff --git a/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx b/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx index 44eee0c0b23f8..190972eeb9327 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx @@ -18,6 +18,8 @@ import { MarkdownRenderer } from '../markdown_editor'; import { FilterOptions } from '../../containers/types'; import { TruncatedText } from '../truncated_text'; import { initialData as initialGetCasesData, useGetCases } from '../../containers/use_get_cases'; +import { useAvailableCasesOwners } from '../app/use_available_owners'; +import { useCasesContext } from '../cases_context/use_cases_context'; const MarkdownContainer = styled.div` max-height: 150px; @@ -31,9 +33,13 @@ export interface RecentCasesProps { } export const RecentCasesComp = ({ filterOptions, maxCasesToShow }: RecentCasesProps) => { + const { owner } = useCasesContext(); + const availableSolutions = useAvailableCasesOwners(['read']); + const hasOwner = !!owner.length; + const { data = initialGetCasesData, isLoading: isLoadingCases } = useGetCases({ queryParams: { perPage: maxCasesToShow }, - filterOptions, + filterOptions: { ...filterOptions, owner: hasOwner ? owner : availableSolutions }, }); return isLoadingCases ? ( diff --git a/x-pack/plugins/cases/public/containers/api.ts b/x-pack/plugins/cases/public/containers/api.ts index 2b7e8910fb9da..697635030eb49 100644 --- a/x-pack/plugins/cases/public/containers/api.ts +++ b/x-pack/plugins/cases/public/containers/api.ts @@ -7,6 +7,7 @@ import type { ValidFeatureId } from '@kbn/rule-data-utils'; import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common/constants'; +import { isEmpty } from 'lodash'; import { Cases, FetchCasesProps, @@ -182,7 +183,7 @@ export const getCases = async ({ ...(filterOptions.status !== StatusAll ? { status: filterOptions.status } : {}), ...(filterOptions.severity !== SeverityAll ? { severity: filterOptions.severity } : {}), assignees: filterOptions.assignees, - reporters: filterOptions.reporters.map((r) => r.username ?? '').filter((r) => r !== ''), + reporters: constructReportersFilter(filterOptions.reporters), tags: filterOptions.tags, ...(filterOptions.search.length > 0 ? { search: filterOptions.search } : {}), ...(filterOptions.searchFields.length > 0 ? { searchFields: filterOptions.searchFields } : {}), @@ -199,6 +200,18 @@ export const getCases = async ({ return convertAllCasesToCamel(decodeCasesFindResponse(response)); }; +export const constructReportersFilter = (reporters: User[]) => { + return reporters + .map((reporter) => { + if (reporter.profile_uid != null) { + return reporter.profile_uid; + } + + return reporter.username ?? ''; + }) + .filter((reporterID) => !isEmpty(reporterID)); +}; + export const postCase = async (newCase: CasePostRequest, signal: AbortSignal): Promise => { const response = await KibanaServices.get().http.fetch(CASES_URL, { method: 'POST', diff --git a/x-pack/plugins/cases/server/telemetry/constants.ts b/x-pack/plugins/cases/server/telemetry/constants.ts new file mode 100644 index 0000000000000..705321e3f1fa0 --- /dev/null +++ b/x-pack/plugins/cases/server/telemetry/constants.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GENERAL_CASES_OWNER, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../common'; + +/** + * This should only be used within telemetry + */ +export const OWNERS = [OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER, GENERAL_CASES_OWNER] as const; diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts index 71c484071017c..2a6aaddf8f5db 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts @@ -7,6 +7,7 @@ import { SavedObjectsFindResponse } from '@kbn/core/server'; import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { CaseAggregationResult } from '../types'; import { getCasesTelemetryData } from './cases'; describe('getCasesTelemetryData', () => { @@ -50,13 +51,33 @@ describe('getCasesTelemetryData', () => { ], }; - mockFind({ + const assignees = { + assigneeFilters: { + buckets: { + atLeastOne: { + doc_count: 0, + }, + zero: { + doc_count: 100, + }, + }, + }, + totalAssignees: { value: 5 }, + }; + + const solutionValues = { + counts, + ...assignees, + }; + + const caseAggsResult: CaseAggregationResult = { users: { value: 1 }, tags: { value: 2 }, + ...assignees, counts, - securitySolution: { counts }, - observability: { counts }, - cases: { counts }, + securitySolution: { ...solutionValues }, + observability: { ...solutionValues }, + cases: { ...solutionValues }, syncAlerts: { buckets: [ { @@ -93,7 +114,9 @@ describe('getCasesTelemetryData', () => { }, ], }, - }); + }; + + mockFind(caseAggsResult); mockFind({ participants: { value: 2 } }); mockFind({ references: { referenceType: { referenceAgg: { value: 3 } } } }); mockFind({ references: { referenceType: { referenceAgg: { value: 4 } } } }); @@ -139,20 +162,40 @@ describe('getCasesTelemetryData', () => { totalUsers: 1, totalWithAlerts: 3, totalWithConnectors: 4, + assignees: { + total: 5, + totalWithZero: 100, + totalWithAtLeastOne: 0, + }, }, main: { + assignees: { + total: 5, + totalWithZero: 100, + totalWithAtLeastOne: 0, + }, total: 1, daily: 3, weekly: 2, monthly: 1, }, obs: { + assignees: { + total: 5, + totalWithZero: 100, + totalWithAtLeastOne: 0, + }, total: 1, daily: 3, weekly: 2, monthly: 1, }, sec: { + assignees: { + total: 5, + totalWithZero: 100, + totalWithAtLeastOne: 0, + }, total: 1, daily: 3, weekly: 2, @@ -166,145 +209,263 @@ describe('getCasesTelemetryData', () => { await getCasesTelemetryData({ savedObjectsClient, logger }); - expect(savedObjectsClient.find.mock.calls[0][0]).toEqual({ - aggs: { - cases: { - aggs: { - counts: { - date_range: { - field: 'cases.attributes.created_at', - format: 'dd/MM/YYYY', - ranges: [ - { - from: 'now-1d', - to: 'now', - }, - { - from: 'now-1w', - to: 'now', + expect(savedObjectsClient.find.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "aggs": Object { + "assigneeFilters": Object { + "filters": Object { + "filters": Object { + "atLeastOne": Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, }, - { - from: 'now-1M', - to: 'now', + }, + "zero": Object { + "bool": Object { + "must_not": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, }, - ], + }, }, }, }, - filter: { - term: { - 'cases.attributes.owner': 'cases', - }, - }, - }, - counts: { - date_range: { - field: 'cases.attributes.created_at', - format: 'dd/MM/YYYY', - ranges: [ - { - from: 'now-1d', - to: 'now', + "cases": Object { + "aggs": Object { + "assigneeFilters": Object { + "filters": Object { + "filters": Object { + "atLeastOne": Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, + "zero": Object { + "bool": Object { + "must_not": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, + }, + }, }, - { - from: 'now-1w', - to: 'now', + "counts": Object { + "date_range": Object { + "field": "cases.attributes.created_at", + "format": "dd/MM/YYYY", + "ranges": Array [ + Object { + "from": "now-1d", + "to": "now", + }, + Object { + "from": "now-1w", + "to": "now", + }, + Object { + "from": "now-1M", + "to": "now", + }, + ], + }, }, - { - from: 'now-1M', - to: 'now', + "totalAssignees": Object { + "value_count": Object { + "field": "cases.attributes.assignees.uid", + }, }, - ], - }, - }, - observability: { - aggs: { - counts: { - date_range: { - field: 'cases.attributes.created_at', - format: 'dd/MM/YYYY', - ranges: [ - { - from: 'now-1d', - to: 'now', - }, - { - from: 'now-1w', - to: 'now', - }, - { - from: 'now-1M', - to: 'now', - }, - ], + }, + "filter": Object { + "term": Object { + "cases.attributes.owner": "cases", }, }, }, - filter: { - term: { - 'cases.attributes.owner': 'observability', + "counts": Object { + "date_range": Object { + "field": "cases.attributes.created_at", + "format": "dd/MM/YYYY", + "ranges": Array [ + Object { + "from": "now-1d", + "to": "now", + }, + Object { + "from": "now-1w", + "to": "now", + }, + Object { + "from": "now-1M", + "to": "now", + }, + ], }, }, - }, - securitySolution: { - aggs: { - counts: { - date_range: { - field: 'cases.attributes.created_at', - format: 'dd/MM/YYYY', - ranges: [ - { - from: 'now-1d', - to: 'now', - }, - { - from: 'now-1w', - to: 'now', + "observability": Object { + "aggs": Object { + "assigneeFilters": Object { + "filters": Object { + "filters": Object { + "atLeastOne": Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, + "zero": Object { + "bool": Object { + "must_not": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, }, - { - from: 'now-1M', - to: 'now', + }, + }, + "counts": Object { + "date_range": Object { + "field": "cases.attributes.created_at", + "format": "dd/MM/YYYY", + "ranges": Array [ + Object { + "from": "now-1d", + "to": "now", + }, + Object { + "from": "now-1w", + "to": "now", + }, + Object { + "from": "now-1M", + "to": "now", + }, + ], + }, + }, + "totalAssignees": Object { + "value_count": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + "filter": Object { + "term": Object { + "cases.attributes.owner": "observability", + }, + }, + }, + "securitySolution": Object { + "aggs": Object { + "assigneeFilters": Object { + "filters": Object { + "filters": Object { + "atLeastOne": Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, + "zero": Object { + "bool": Object { + "must_not": Object { + "exists": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + }, }, - ], + }, + }, + "counts": Object { + "date_range": Object { + "field": "cases.attributes.created_at", + "format": "dd/MM/YYYY", + "ranges": Array [ + Object { + "from": "now-1d", + "to": "now", + }, + Object { + "from": "now-1w", + "to": "now", + }, + Object { + "from": "now-1M", + "to": "now", + }, + ], + }, + }, + "totalAssignees": Object { + "value_count": Object { + "field": "cases.attributes.assignees.uid", + }, + }, + }, + "filter": Object { + "term": Object { + "cases.attributes.owner": "securitySolution", }, }, }, - filter: { - term: { - 'cases.attributes.owner': 'securitySolution', + "status": Object { + "terms": Object { + "field": "cases.attributes.status", }, }, - }, - status: { - terms: { - field: 'cases.attributes.status', + "syncAlerts": Object { + "terms": Object { + "field": "cases.attributes.settings.syncAlerts", + }, }, - }, - syncAlerts: { - terms: { - field: 'cases.attributes.settings.syncAlerts', + "tags": Object { + "cardinality": Object { + "field": "cases.attributes.tags", + }, }, - }, - tags: { - cardinality: { - field: 'cases.attributes.tags', + "totalAssignees": Object { + "value_count": Object { + "field": "cases.attributes.assignees.uid", + }, }, - }, - totalsByOwner: { - terms: { - field: 'cases.attributes.owner', + "totalsByOwner": Object { + "terms": Object { + "field": "cases.attributes.owner", + }, }, - }, - users: { - cardinality: { - field: 'cases.attributes.created_by.username', + "users": Object { + "cardinality": Object { + "field": "cases.attributes.created_by.username", + }, }, }, - }, - page: 0, - perPage: 0, - type: 'cases', - }); + "page": 0, + "perPage": 0, + "type": "cases", + } + `); expect(savedObjectsClient.find.mock.calls[1][0]).toEqual({ aggs: { diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.ts index 3af4a97c07a2b..3969ed77e5a17 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.ts @@ -11,6 +11,7 @@ import { CASE_USER_ACTION_SAVED_OBJECT, } from '../../../common/constants'; import { ESCaseAttributes } from '../../services/cases/types'; +import { OWNERS } from '../constants'; import { CollectTelemetryDataParams, Buckets, @@ -18,6 +19,7 @@ import { Cardinality, ReferencesAggregation, LatestDates, + CaseAggregationResult, } from '../types'; import { findValueInBuckets, @@ -27,6 +29,7 @@ import { getOnlyAlertsCommentsFilter, getOnlyConnectorsFilter, getReferencesAggregationQuery, + getSolutionValues, } from './utils'; export const getLatestCasesDates = async ({ @@ -58,8 +61,7 @@ export const getCasesTelemetryData = async ({ savedObjectsClient, logger, }: CollectTelemetryDataParams): Promise => { - const owners = ['observability', 'securitySolution', 'cases'] as const; - const byOwnerAggregationQuery = owners.reduce( + const byOwnerAggregationQuery = OWNERS.reduce( (aggQuery, owner) => ({ ...aggQuery, [owner]: { @@ -68,28 +70,23 @@ export const getCasesTelemetryData = async ({ [`${CASE_SAVED_OBJECT}.attributes.owner`]: owner, }, }, - aggs: getCountsAggregationQuery(CASE_SAVED_OBJECT), + aggs: { + ...getCountsAggregationQuery(CASE_SAVED_OBJECT), + ...getAssigneesAggregations(), + }, }, }), {} ); - const casesRes = await savedObjectsClient.find< - unknown, - Record & { - counts: Buckets; - syncAlerts: Buckets; - status: Buckets; - users: Cardinality; - tags: Cardinality; - } - >({ + const casesRes = await savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_SAVED_OBJECT, aggs: { ...byOwnerAggregationQuery, ...getCountsAggregationQuery(CASE_SAVED_OBJECT), + ...getAssigneesAggregations(), totalsByOwner: { terms: { field: `${CASE_SAVED_OBJECT}.attributes.owner` }, }, @@ -116,7 +113,7 @@ export const getCasesTelemetryData = async ({ const commentsRes = await savedObjectsClient.find< unknown, - Record & { + Record & { participants: Cardinality; } & ReferencesAggregation >({ @@ -164,16 +161,7 @@ export const getCasesTelemetryData = async ({ const aggregationsBuckets = getAggregationsBuckets({ aggs: casesRes.aggregations, - keys: [ - 'counts', - 'observability.counts', - 'securitySolution.counts', - 'cases.counts', - 'syncAlerts', - 'status', - 'totalsByOwner', - 'users', - ], + keys: ['counts', 'syncAlerts', 'status', 'users', 'totalAssignees'], }); return { @@ -195,18 +183,47 @@ export const getCasesTelemetryData = async ({ totalWithConnectors: totalConnectorsRes.aggregations?.references?.referenceType?.referenceAgg?.value ?? 0, latestDates, + assignees: { + total: casesRes.aggregations?.totalAssignees.value ?? 0, + totalWithZero: casesRes.aggregations?.assigneeFilters.buckets.zero.doc_count ?? 0, + totalWithAtLeastOne: + casesRes.aggregations?.assigneeFilters.buckets.atLeastOne.doc_count ?? 0, + }, }, - sec: { - total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'securitySolution'), - ...getCountsFromBuckets(aggregationsBuckets['securitySolution.counts']), - }, - obs: { - total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'observability'), - ...getCountsFromBuckets(aggregationsBuckets['observability.counts']), - }, - main: { - total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'cases'), - ...getCountsFromBuckets(aggregationsBuckets['cases.counts']), - }, + sec: getSolutionValues(casesRes.aggregations, 'securitySolution'), + obs: getSolutionValues(casesRes.aggregations, 'observability'), + main: getSolutionValues(casesRes.aggregations, 'cases'), }; }; + +const getAssigneesAggregations = () => ({ + totalAssignees: { + value_count: { + field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`, + }, + }, + assigneeFilters: { + filters: { + filters: { + zero: { + bool: { + must_not: { + exists: { + field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`, + }, + }, + }, + }, + atLeastOne: { + bool: { + filter: { + exists: { + field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`, + }, + }, + }, + }, + }, + }, + }, +}); diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts index f10cae4afb611..3b1bd17b28cdf 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts @@ -6,6 +6,7 @@ */ import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; +import { CaseAggregationResult } from '../types'; import { findValueInBuckets, getAggregationsBuckets, @@ -18,9 +19,127 @@ import { getOnlyAlertsCommentsFilter, getOnlyConnectorsFilter, getReferencesAggregationQuery, + getSolutionValues, } from './utils'; describe('utils', () => { + describe('getSolutionValues', () => { + const counts = { + buckets: [ + { doc_count: 1, key: 1 }, + { doc_count: 2, key: 2 }, + { doc_count: 3, key: 3 }, + ], + }; + + const assignees = { + assigneeFilters: { + buckets: { + atLeastOne: { + doc_count: 0, + }, + zero: { + doc_count: 100, + }, + }, + }, + totalAssignees: { value: 5 }, + }; + + const solutionValues = { + counts, + ...assignees, + }; + + const aggsResult: CaseAggregationResult = { + users: { value: 1 }, + tags: { value: 2 }, + ...assignees, + counts, + securitySolution: { ...solutionValues }, + observability: { ...solutionValues }, + cases: { ...solutionValues }, + syncAlerts: { + buckets: [ + { + key: 0, + doc_count: 1, + }, + { + key: 1, + doc_count: 1, + }, + ], + }, + status: { + buckets: [ + { + key: 'open', + doc_count: 2, + }, + ], + }, + totalsByOwner: { + buckets: [ + { + key: 'observability', + doc_count: 1, + }, + { + key: 'securitySolution', + doc_count: 1, + }, + { + key: 'cases', + doc_count: 1, + }, + ], + }, + }; + + it('constructs the solution values correctly', () => { + expect(getSolutionValues(aggsResult, 'securitySolution')).toMatchInlineSnapshot(` + Object { + "assignees": Object { + "total": 5, + "totalWithAtLeastOne": 0, + "totalWithZero": 100, + }, + "daily": 3, + "monthly": 1, + "total": 1, + "weekly": 2, + } + `); + expect(getSolutionValues(aggsResult, 'cases')).toMatchInlineSnapshot(` + Object { + "assignees": Object { + "total": 5, + "totalWithAtLeastOne": 0, + "totalWithZero": 100, + }, + "daily": 3, + "monthly": 1, + "total": 1, + "weekly": 2, + } + `); + expect(getSolutionValues(aggsResult, 'observability')).toMatchInlineSnapshot(` + Object { + "assignees": Object { + "total": 5, + "totalWithAtLeastOne": 0, + "totalWithZero": 100, + }, + "daily": 3, + "monthly": 1, + "total": 1, + "weekly": 2, + } + `); + }); + }); + describe('getCountsAggregationQuery', () => { it('returns the correct query', () => { expect(getCountsAggregationQuery('test')).toEqual({ diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.ts index 70e719ef9fa31..dd542b5f65229 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.ts @@ -13,8 +13,15 @@ import { CASE_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT, } from '../../../common/constants'; -import { Buckets, CasesTelemetry, MaxBucketOnCaseAggregation } from '../types'; +import { + CaseAggregationResult, + Buckets, + CasesTelemetry, + MaxBucketOnCaseAggregation, + SolutionTelemetry, +} from '../types'; import { buildFilter } from '../../client/utils'; +import { OWNERS } from '../constants'; export const getCountsAggregationQuery = (savedObjectType: string) => ({ counts: { @@ -147,6 +154,26 @@ export const getBucketFromAggregation = ({ aggs?: Record; }): Buckets['buckets'] => (get(aggs, `${key}.buckets`) ?? []) as Buckets['buckets']; +export const getSolutionValues = ( + aggregations: CaseAggregationResult | undefined, + owner: typeof OWNERS[number] +): SolutionTelemetry => { + const aggregationsBuckets = getAggregationsBuckets({ + aggs: aggregations, + keys: ['totalsByOwner', 'securitySolution.counts', 'observability.counts', 'cases.counts'], + }); + + return { + total: findValueInBuckets(aggregationsBuckets.totalsByOwner, owner), + ...getCountsFromBuckets(aggregationsBuckets[`${owner}.counts`]), + assignees: { + total: aggregations?.[owner].totalAssignees.value ?? 0, + totalWithZero: aggregations?.[owner].assigneeFilters.buckets.zero.doc_count ?? 0, + totalWithAtLeastOne: aggregations?.[owner].assigneeFilters.buckets.atLeastOne.doc_count ?? 0, + }, + }; +}; + export const findValueInBuckets = (buckets: Buckets['buckets'], value: string | number): number => buckets.find(({ key }) => key === value)?.doc_count ?? 0; @@ -184,6 +211,11 @@ export const getOnlyConnectorsFilter = () => export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ cases: { all: { + assignees: { + total: 0, + totalWithZero: 0, + totalWithAtLeastOne: 0, + }, total: 0, monthly: 0, weekly: 0, @@ -206,9 +238,27 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ closedAt: null, }, }, - sec: { total: 0, monthly: 0, weekly: 0, daily: 0 }, - obs: { total: 0, monthly: 0, weekly: 0, daily: 0 }, - main: { total: 0, monthly: 0, weekly: 0, daily: 0 }, + sec: { + total: 0, + monthly: 0, + weekly: 0, + daily: 0, + assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + }, + obs: { + total: 0, + monthly: 0, + weekly: 0, + daily: 0, + assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + }, + main: { + total: 0, + monthly: 0, + weekly: 0, + daily: 0, + assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + }, }, userActions: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } }, comments: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } }, diff --git a/x-pack/plugins/cases/server/telemetry/schema.ts b/x-pack/plugins/cases/server/telemetry/schema.ts index 5b8b75cc01833..fa04fc7c6651d 100644 --- a/x-pack/plugins/cases/server/telemetry/schema.ts +++ b/x-pack/plugins/cases/server/telemetry/schema.ts @@ -12,6 +12,8 @@ import { StatusSchema, LatestDatesSchema, TypeString, + SolutionTelemetrySchema, + AssigneesSchema, } from './types'; const long: TypeLong = { type: 'long' }; @@ -24,6 +26,17 @@ const countSchema: CountSchema = { daily: long, }; +const assigneesSchema: AssigneesSchema = { + total: long, + totalWithZero: long, + totalWithAtLeastOne: long, +}; + +const solutionTelemetry: SolutionTelemetrySchema = { + ...countSchema, + assignees: assigneesSchema, +}; + const statusSchema: StatusSchema = { open: long, inProgress: long, @@ -40,6 +53,7 @@ export const casesSchema: CasesTelemetrySchema = { cases: { all: { ...countSchema, + assignees: assigneesSchema, status: statusSchema, syncAlertsOn: long, syncAlertsOff: long, @@ -50,9 +64,9 @@ export const casesSchema: CasesTelemetrySchema = { totalWithConnectors: long, latestDates: latestDatesSchema, }, - sec: countSchema, - obs: countSchema, - main: countSchema, + sec: solutionTelemetry, + obs: solutionTelemetry, + main: solutionTelemetry, }, userActions: { all: { ...countSchema, maxOnACase: long } }, comments: { all: { ...countSchema, maxOnACase: long } }, diff --git a/x-pack/plugins/cases/server/telemetry/types.ts b/x-pack/plugins/cases/server/telemetry/types.ts index ec880855f3302..2c8e848b3854f 100644 --- a/x-pack/plugins/cases/server/telemetry/types.ts +++ b/x-pack/plugins/cases/server/telemetry/types.ts @@ -7,6 +7,7 @@ import { ISavedObjectsRepository, Logger } from '@kbn/core/server'; import { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server'; +import { OWNERS } from './constants'; export interface Buckets { buckets: Array<{ @@ -19,6 +20,8 @@ export interface Cardinality { value: number; } +export type ValueCount = Cardinality; + export interface MaxBucketOnCaseAggregation { references: { cases: { max: { value: number } } }; } @@ -47,6 +50,41 @@ export interface Count { daily: number; } +export interface AssigneesFilters { + buckets: { + zero: { doc_count: number }; + atLeastOne: { doc_count: number }; + }; +} + +export type CaseAggregationResult = Record< + typeof OWNERS[number], + { + counts: Buckets; + totalAssignees: ValueCount; + assigneeFilters: AssigneesFilters; + } +> & { + assigneeFilters: AssigneesFilters; + counts: Buckets; + syncAlerts: Buckets; + status: Buckets; + users: Cardinality; + tags: Cardinality; + totalAssignees: ValueCount; + totalsByOwner: Buckets; +}; + +export interface Assignees { + total: number; + totalWithZero: number; + totalWithAtLeastOne: number; +} + +export interface SolutionTelemetry extends Count { + assignees: Assignees; +} + export interface Status { open: number; inProgress: number; @@ -62,6 +100,7 @@ export interface LatestDates { export interface CasesTelemetry { cases: { all: Count & { + assignees: Assignees; status: Status; syncAlertsOn: number; syncAlertsOff: number; @@ -72,9 +111,9 @@ export interface CasesTelemetry { totalWithConnectors: number; latestDates: LatestDates; }; - sec: Count; - obs: Count; - main: Count; + sec: SolutionTelemetry; + obs: SolutionTelemetry; + main: SolutionTelemetry; }; userActions: { all: Count & { maxOnACase: number } }; comments: { all: Count & { maxOnACase: number } }; @@ -107,3 +146,5 @@ export type CountSchema = MakeSchemaFrom; export type StatusSchema = MakeSchemaFrom; export type LatestDatesSchema = MakeSchemaFrom; export type CasesTelemetrySchema = MakeSchemaFrom; +export type AssigneesSchema = MakeSchemaFrom; +export type SolutionTelemetrySchema = MakeSchemaFrom; diff --git a/x-pack/plugins/cloud/kibana.json b/x-pack/plugins/cloud/kibana.json index 8e5fc4e0f11e1..51df5d20d81b9 100644 --- a/x-pack/plugins/cloud/kibana.json +++ b/x-pack/plugins/cloud/kibana.json @@ -7,7 +7,7 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "cloud"], - "optionalPlugins": ["usageCollection", "home", "security"], + "optionalPlugins": ["cloudExperiments", "usageCollection", "home", "security"], "server": true, "ui": true } diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts index 2215fbd64fe98..599dee5e707b7 100644 --- a/x-pack/plugins/cloud/public/plugin.test.ts +++ b/x-pack/plugins/cloud/public/plugin.test.ts @@ -12,6 +12,21 @@ import { coreMock } from '@kbn/core/public/mocks'; import { homePluginMock } from '@kbn/home-plugin/public/mocks'; import { securityMock } from '@kbn/security-plugin/public/mocks'; import { CloudPlugin, type CloudConfigType } from './plugin'; +import { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common'; +import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks'; + +const baseConfig = { + base_url: 'https://cloud.elastic.co', + deployment_url: '/abc123', + profile_url: '/user/settings/', + organization_url: '/account/', + full_story: { + enabled: false, + }, + chat: { + enabled: false, + }, +}; describe('Cloud Plugin', () => { describe('#setup', () => { @@ -22,17 +37,8 @@ describe('Cloud Plugin', () => { const setupPlugin = async ({ config = {} }: { config?: Partial }) => { const initContext = coreMock.createPluginInitializerContext({ + ...baseConfig, id: 'cloudId', - base_url: 'https://cloud.elastic.co', - deployment_url: '/abc123', - profile_url: '/profile/alice', - organization_url: '/org/myOrg', - full_story: { - enabled: false, - }, - chat: { - enabled: false, - }, ...config, }); @@ -92,16 +98,7 @@ describe('Cloud Plugin', () => { currentUserProps?: Record | Error; }) => { const initContext = coreMock.createPluginInitializerContext({ - base_url: 'https://cloud.elastic.co', - deployment_url: '/abc123', - profile_url: '/profile/alice', - organization_url: '/org/myOrg', - full_story: { - enabled: false, - }, - chat: { - enabled: false, - }, + ...baseConfig, ...config, }); @@ -249,17 +246,8 @@ describe('Cloud Plugin', () => { failHttp?: boolean; }) => { const initContext = coreMock.createPluginInitializerContext({ + ...baseConfig, id: isCloudEnabled ? 'cloud-id' : null, - base_url: 'https://cloud.elastic.co', - deployment_url: '/abc123', - profile_url: '/profile/alice', - organization_url: '/org/myOrg', - full_story: { - enabled: false, - }, - chat: { - enabled: false, - }, ...config, }); @@ -322,18 +310,9 @@ describe('Cloud Plugin', () => { describe('interface', () => { const setupPlugin = () => { const initContext = coreMock.createPluginInitializerContext({ + ...baseConfig, id: 'cloudId', cname: 'cloud.elastic.co', - base_url: 'https://cloud.elastic.co', - deployment_url: '/abc123', - profile_url: '/user/settings/', - organization_url: '/account/', - chat: { - enabled: false, - }, - full_story: { - enabled: false, - }, }); const plugin = new CloudPlugin(initContext); @@ -383,6 +362,50 @@ describe('Cloud Plugin', () => { expect(setup.cname).toBe('cloud.elastic.co'); }); }); + + describe('Set up cloudExperiments', () => { + describe('when cloud ID is not provided in the config', () => { + let cloudExperiments: jest.Mocked; + beforeEach(() => { + const plugin = new CloudPlugin(coreMock.createPluginInitializerContext(baseConfig)); + cloudExperiments = cloudExperimentsMock.createSetupMock(); + plugin.setup(coreMock.createSetup(), { cloudExperiments }); + }); + + test('does not call cloudExperiments.identifyUser', async () => { + expect(cloudExperiments.identifyUser).not.toHaveBeenCalled(); + }); + }); + + describe('when cloud ID is provided in the config', () => { + let cloudExperiments: jest.Mocked; + beforeEach(() => { + const plugin = new CloudPlugin( + coreMock.createPluginInitializerContext({ ...baseConfig, id: 'cloud test' }) + ); + cloudExperiments = cloudExperimentsMock.createSetupMock(); + plugin.setup(coreMock.createSetup(), { cloudExperiments }); + }); + + test('calls cloudExperiments.identifyUser', async () => { + expect(cloudExperiments.identifyUser).toHaveBeenCalledTimes(1); + }); + + test('the cloud ID is hashed when calling cloudExperiments.identifyUser', async () => { + expect(cloudExperiments.identifyUser.mock.calls[0][0]).toEqual( + '1acb4a1cc1c3d672a8d826055d897c2623ceb1d4fb07e46d97986751a36b06cf' + ); + }); + + test('specifies the Kibana version when calling cloudExperiments.identifyUser', async () => { + expect(cloudExperiments.identifyUser.mock.calls[0][1]).toEqual( + expect.objectContaining({ + kibanaVersion: 'version', + }) + ); + }); + }); + }); }); describe('#start', () => { diff --git a/x-pack/plugins/cloud/public/plugin.tsx b/x-pack/plugins/cloud/public/plugin.tsx index 195e359436e28..c27668feb09bd 100644 --- a/x-pack/plugins/cloud/public/plugin.tsx +++ b/x-pack/plugins/cloud/public/plugin.tsx @@ -22,6 +22,7 @@ import { BehaviorSubject, catchError, from, map, of } from 'rxjs'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { Sha256 } from '@kbn/crypto-browser'; +import type { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common'; import { registerCloudDeploymentIdAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { @@ -58,6 +59,7 @@ export interface CloudConfigType { interface CloudSetupDependencies { home?: HomePublicPluginSetup; security?: Pick; + cloudExperiments?: CloudExperimentsPluginSetup; } interface CloudStartDependencies { @@ -93,15 +95,15 @@ interface SetupChatDeps extends Pick { export class CloudPlugin implements Plugin { private readonly config: CloudConfigType; - private isCloudEnabled: boolean; + private readonly isCloudEnabled: boolean; private chatConfig$ = new BehaviorSubject({ enabled: false }); constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); - this.isCloudEnabled = false; + this.isCloudEnabled = getIsCloudEnabled(this.config.id); } - public setup(core: CoreSetup, { home, security }: CloudSetupDependencies) { + public setup(core: CoreSetup, { cloudExperiments, home, security }: CloudSetupDependencies) { this.setupTelemetryContext(core.analytics, security, this.config.id); this.setupFullStory({ analytics: core.analytics, basePath: core.http.basePath }).catch((e) => @@ -118,7 +120,12 @@ export class CloudPlugin implements Plugin { base_url: baseUrl, } = this.config; - this.isCloudEnabled = getIsCloudEnabled(id); + if (this.isCloudEnabled && id) { + // We use the Hashed Cloud Deployment ID as the userId in the Cloud Experiments + cloudExperiments?.identifyUser(sha256(id), { + kibanaVersion: this.initializerContext.env.packageInfo.version, + }); + } this.setupChat({ http: core.http, security }).catch((e) => // eslint-disable-next-line no-console diff --git a/x-pack/plugins/cloud/server/plugin.test.ts b/x-pack/plugins/cloud/server/plugin.test.ts index ccb0b8545fcf6..05109a4c54816 100644 --- a/x-pack/plugins/cloud/server/plugin.test.ts +++ b/x-pack/plugins/cloud/server/plugin.test.ts @@ -10,6 +10,8 @@ import { CloudPlugin } from './plugin'; import { config } from './config'; import { securityMock } from '@kbn/security-plugin/server/mocks'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; +import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks'; +import { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common'; describe('Cloud Plugin', () => { describe('#setup', () => { @@ -66,5 +68,51 @@ describe('Cloud Plugin', () => { expect(securityDependencyMock.setIsElasticCloudDeployment).toHaveBeenCalledTimes(1); }); }); + + describe('Set up cloudExperiments', () => { + describe('when cloud ID is not provided in the config', () => { + let cloudExperiments: jest.Mocked; + beforeEach(() => { + const plugin = new CloudPlugin( + coreMock.createPluginInitializerContext(config.schema.validate({})) + ); + cloudExperiments = cloudExperimentsMock.createSetupMock(); + plugin.setup(coreMock.createSetup(), { cloudExperiments }); + }); + + test('does not call cloudExperiments.identifyUser', async () => { + expect(cloudExperiments.identifyUser).not.toHaveBeenCalled(); + }); + }); + + describe('when cloud ID is provided in the config', () => { + let cloudExperiments: jest.Mocked; + beforeEach(() => { + const plugin = new CloudPlugin( + coreMock.createPluginInitializerContext(config.schema.validate({ id: 'cloud test' })) + ); + cloudExperiments = cloudExperimentsMock.createSetupMock(); + plugin.setup(coreMock.createSetup(), { cloudExperiments }); + }); + + test('calls cloudExperiments.identifyUser', async () => { + expect(cloudExperiments.identifyUser).toHaveBeenCalledTimes(1); + }); + + test('the cloud ID is hashed when calling cloudExperiments.identifyUser', async () => { + expect(cloudExperiments.identifyUser.mock.calls[0][0]).toEqual( + '1acb4a1cc1c3d672a8d826055d897c2623ceb1d4fb07e46d97986751a36b06cf' + ); + }); + + test('specifies the Kibana version when calling cloudExperiments.identifyUser', async () => { + expect(cloudExperiments.identifyUser.mock.calls[0][1]).toEqual( + expect.objectContaining({ + kibanaVersion: 'version', + }) + ); + }); + }); + }); }); }); diff --git a/x-pack/plugins/cloud/server/plugin.ts b/x-pack/plugins/cloud/server/plugin.ts index 8d5c38477d0cb..d38a57a4d3bab 100644 --- a/x-pack/plugins/cloud/server/plugin.ts +++ b/x-pack/plugins/cloud/server/plugin.ts @@ -8,6 +8,8 @@ import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { CoreSetup, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server'; import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; +import type { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common'; +import { createSHA256Hash } from '@kbn/crypto'; import { registerCloudDeploymentIdAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context'; import { CloudConfigType } from './config'; import { registerCloudUsageCollector } from './collectors'; @@ -20,6 +22,7 @@ import { readInstanceSizeMb } from './env'; interface PluginsSetup { usageCollection?: UsageCollectionSetup; security?: SecurityPluginSetup; + cloudExperiments?: CloudExperimentsPluginSetup; } export interface CloudSetup { @@ -44,7 +47,10 @@ export class CloudPlugin implements Plugin { this.isDev = this.context.env.mode.dev; } - public setup(core: CoreSetup, { usageCollection, security }: PluginsSetup): CloudSetup { + public setup( + core: CoreSetup, + { cloudExperiments, usageCollection, security }: PluginsSetup + ): CloudSetup { this.logger.debug('Setting up Cloud plugin'); const isCloudEnabled = getIsCloudEnabled(this.config.id); registerCloudDeploymentIdAnalyticsContext(core.analytics, this.config.id); @@ -54,6 +60,13 @@ export class CloudPlugin implements Plugin { security?.setIsElasticCloudDeployment(); } + if (isCloudEnabled && this.config.id) { + // We use the Cloud ID as the userId in the Cloud Experiments + cloudExperiments?.identifyUser(createSHA256Hash(this.config.id), { + kibanaVersion: this.context.env.packageInfo.version, + }); + } + if (this.config.full_story.enabled) { registerFullstoryRoute({ httpResources: core.http.resources, diff --git a/x-pack/plugins/cloud/tsconfig.json b/x-pack/plugins/cloud/tsconfig.json index e743b46ac17eb..d8c8a5c8eca44 100644 --- a/x-pack/plugins/cloud/tsconfig.json +++ b/x-pack/plugins/cloud/tsconfig.json @@ -17,6 +17,7 @@ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, { "path": "../../../src/plugins/home/tsconfig.json" }, + { "path": "../cloud_integrations/cloud_experiments/tsconfig.json" }, { "path": "../security/tsconfig.json" }, ] } diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/README.mdx b/x-pack/plugins/cloud_integrations/cloud_experiments/README.mdx new file mode 100755 index 0000000000000..96f0a3ed4ca50 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/README.mdx @@ -0,0 +1,179 @@ +--- +id: kibCloudExperimentsPlugin +slug: /kibana-dev-docs/key-concepts/cloud-experiments-plugin +title: Cloud Experiments service +description: The Cloud Experiments Service provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments. +date: 2022-09-07 +tags: ['kibana', 'dev', 'contributor', 'api docs', 'cloud', 'a/b testing', 'experiments'] +--- + +# Kibana Cloud Experiments Service + +The Cloud Experiments Service provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments. + +The `cloudExperiments` plugin is disabled by default and only enabled on Elastic Cloud deployments. + +## Public API + +If you are developing a feature that needs to use a feature flag, or you are implementing an A/B-testing scenario, this is how you should fetch the value of your feature flags (for either server and browser side code): + +First, you should declare the optional dependency on this plugin. Do not list it in your `requiredPlugins`, as this plugin is disabled by default and only enabled in Cloud deployments. Adding it to your `requiredPlugins` will cause Kibana to refuse to start by default. + +```json +// plugin/kibana.json +{ + "id": "myPlugin", + "optionalPlugins": ["cloudExperiments"] +} +``` + +Please, be aware that your plugin will run even when the `cloudExperiment` plugin is disabled. Make sure to declare it as an optional dependency in your plugin's TypeScript contract to remind you that it might not always be available. + +### Fetching the value of the feature flags + +First, make sure that your feature flag is listed in [`FEATURE_FLAG_NAMES`](./common/constants.ts). +Then, you can fetch the value of your feature flag by using the API `cloudExperiments.getVariation` as follows: + +```ts +import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/(public|server)'; +import type { + CloudExperimentsPluginSetup, + CloudExperimentsPluginStart +} from '@kbn/cloud-experiments-plugin/common'; + +interface SetupDeps { + cloudExperiments?: CloudExperimentsPluginSetup; +} + +interface StartDeps { + cloudExperiments?: CloudExperimentsPluginStart; +} + +export class MyPlugin implements Plugin { + public setup(core: CoreSetup, deps: SetupDeps) { + this.doSomethingBasedOnFeatureFlag(deps.cloudExperiments); + } + + public start(core: CoreStart, deps: StartDeps) { + this.doSomethingBasedOnFeatureFlag(deps.cloudExperiments); + } + + private async doSomethingBasedOnFeatureFlag(cloudExperiments?: CloudExperimentsPluginStart) { + let myConfig = 'default config'; + if (cloudExperiments) { + myConfig = await cloudExperiments.getVariation( + 'my-plugin.my-feature-flag', // The key 'my-plugin.my-feature-flag' should exist in FEATURE_FLAG_NAMES + 'default config' + ); + } + // do something with the final value of myConfig... + } +} +``` + +Since the `getVariation` API returns a promise, when using it in a React component, you may want to use the hook `useEffect`. + +```tsx +import React, { useEffect, useState } from 'react'; +import type { + CloudExperimentsFeatureFlagNames, + CloudExperimentsPluginStart +} from '@kbn/cloud-experiments-plugin/common'; + +interface Props { + cloudExperiments?: CloudExperimentsPluginStart; +} + +const useVariation = ( + cloudExperiments: CloudExperimentsPluginStart | undefined, + featureFlagName: CloudExperimentsFeatureFlagNames, + defaultValue: Data, + setter: (value: Data) => void +) => { + useEffect(() => { + (async function loadVariation() { + const variationUrl = await cloudExperiments?.getVariation(featureFlagName, defaultValue); + if (variationUrl) { + setter(variationUrl); + } + })(); + }, [cloudExperiments, featureFlagName, defaultValue, setter]); +}; + +export const MyReactComponent: React.FC = ({ cloudExperiments }: Props) => { + const [myConfig, setMyConfig] = useState('default config'); + useVariation( + cloudExperiments, + 'my-plugin.my-feature-flag', // The key 'my-plugin.my-feature-flag' should exist in FEATURE_FLAG_NAMES + 'default config', + setMyConfig + ); + + // use myConfig in the component... +} +``` + +### Reporting metrics + +Experiments require feedback to analyze which variation to the feature flag is the most successful. For this reason, we need to report some metrics defined in the success criteria of the experiment (check back with your PM if they are unclear). + +Our A/B testing provider allows some high-level analysis of the experiment based on the metrics. It also has some limitations about how it handles some type of metrics like number of objects or size of indices. For this reason, you might want to consider shipping the metrics via our usual telemetry channels (`core.analytics` for event-based metrics, or ). + +However, if our A/B testing provider's analysis tool is good enough for your use case, you can use the api `reportMetric` as follows. + +First, make sure to add the metric name in [`METRIC_NAMES`](./common/constants.ts). Then you can use it like below: + +```ts +import type { CoreStart, Plugin } from '@kbn/core/(public|server)'; +import type { + CloudExperimentsPluginSetup, + CloudExperimentsPluginStart +} from '@kbn/cloud-experiments-plugin/common'; + +interface SetupDeps { + cloudExperiments?: CloudExperimentsPluginSetup; +} + +interface StartDeps { + cloudExperiments?: CloudExperimentsPluginStart; +} + +export class MyPlugin implements Plugin { + public start(core: CoreStart, deps: StartDeps) { + // whenever we need to report any metrics: + // the user performed some action, + // or a metric hit a threshold we want to communicate about + deps.cloudExperiments?.reportMetric({ + name: 'Something happened', // The key 'Something happened' should exist in METRIC_NAMES + value: 22, // (optional) in case the metric requires a numeric metric + meta: { // Optional metadata. + hadSomething: true, + userType: 'type 1', + otherNumericField: 1, + } + }) + } +} +``` + +### Testing + +To test your code locally when developing the A/B scenarios, this plugin accepts a custom config to skip the A/B provider calls and return the values. Use the following `kibana.dev.yml` configuration as an example: + +```yml +xpack.cloud_integrations.experiments.enabled: true +xpack.cloud_integrations.experiments.flag_overrides: + "my-plugin.my-feature-flag": "my custom value" +``` + +### How is my user identified? + +The user is automatically identified during the `setup` phase. It currently uses a hash of the deployment ID, meaning all users accessing the same deployment will get the same values for the `getVariation` requests unless the A/B provider is explicitly configured to randomize it. + +If you are curious of the data provided to the `identify` call, you can see that in the [`cloud` plugin](../../cloud). + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.test.ts new file mode 100644 index 0000000000000..8ff277b4abe59 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FEATURE_FLAG_NAMES, METRIC_NAMES } from './constants'; + +function removeDuplicates(obj: Record) { + return [...new Set(Object.values(obj))]; +} + +describe('constants', () => { + describe('FEATURE_FLAG_NAMES', () => { + test('the values should not include duplicates', () => { + expect(Object.values(FEATURE_FLAG_NAMES)).toStrictEqual(removeDuplicates(FEATURE_FLAG_NAMES)); + }); + }); + describe('METRIC_NAMES', () => { + test('the values should not include duplicates', () => { + expect(Object.values(METRIC_NAMES)).toStrictEqual(removeDuplicates(METRIC_NAMES)); + }); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.ts new file mode 100644 index 0000000000000..0cbfeb509e8b3 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.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. + */ + +/** + * List of feature flag names used in Kibana. + * + * Feel free to add/remove entries if needed. + * + * As a convention, the key and the value have the same string. + * + * @remarks Kept centralized in this place to serve as a repository + * to help devs understand if there is someone else already using it. + */ +export enum FEATURE_FLAG_NAMES { + /** + * Used in the Security Solutions onboarding page. + * It resolves the URL that the button "Add Integrations" will point to. + */ + 'security-solutions.add-integrations-url' = 'security-solutions.add-integrations-url', +} + +/** + * List of LaunchDarkly metric names used in Kibana. + * + * Feel free to add/remove entries if needed. + * + * As a convention, the key and the value have the same string. + * + * @remarks Kept centralized in this place to serve as a repository + * to help devs understand if there is someone else already using it. + */ +export enum METRIC_NAMES {} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/index.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/index.ts new file mode 100755 index 0000000000000..1078d980d4b82 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/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 type { + CloudExperimentsMetric, + CloudExperimentsMetricNames, + CloudExperimentsPluginStart, + CloudExperimentsPluginSetup, + CloudExperimentsFeatureFlagNames, +} from './types'; diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/mocks.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/mocks.ts new file mode 100644 index 0000000000000..ebfa14a074dec --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/mocks.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 type { CloudExperimentsPluginSetup, CloudExperimentsPluginStart } from './types'; + +function createStartMock(): jest.Mocked { + return { + getVariation: jest.fn(), + reportMetric: jest.fn(), + }; +} + +function createSetupMock(): jest.Mocked { + return { + identifyUser: jest.fn(), + }; +} + +export const cloudExperimentsMock = { + createSetupMock, + createStartMock, +}; diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts new file mode 100755 index 0000000000000..225f39a11f0c1 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/types.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 { FEATURE_FLAG_NAMES, METRIC_NAMES } from './constants'; + +/** + * The contract of the setup lifecycle method. + * + * @public + */ +export interface CloudExperimentsPluginSetup { + /** + * Identifies the user in the A/B testing service. + * For now, we only rely on the user ID. In the future, we may request further details for more targeted experiments. + * @param userId The unique identifier of the user in the experiment. + * @param userMetadata Additional attributes to the user. Take care to ensure these values do not contain PII. + * + * @deprecated This API will become internal as soon as we reduce the dependency graph of the `cloud` plugin, + * and this plugin depends on it to fetch the data. + */ + identifyUser: ( + userId: string, + userMetadata?: Record> + ) => void; +} + +/** + * The names of the feature flags declared in Kibana. + * Valid keys are defined in {@link FEATURE_FLAG_NAMES}. When using a new feature flag, add the name to the list. + * + * @public + */ +export type CloudExperimentsFeatureFlagNames = keyof typeof FEATURE_FLAG_NAMES; + +/** + * The contract of the start lifecycle method + * + * @public + */ +export interface CloudExperimentsPluginStart { + /** + * Fetch the configuration assigned to variation `configKey`. If nothing is found, fallback to `defaultValue`. + * @param featureFlagName The name of the key to find the config variation. {@link CloudExperimentsFeatureFlagNames}. + * @param defaultValue The fallback value in case no variation is found. + * + * @public + */ + getVariation: ( + featureFlagName: CloudExperimentsFeatureFlagNames, + defaultValue: Data + ) => Promise; + /** + * Report metrics back to the A/B testing service to measure the conversion rate for each variation in the experiment. + * @param metric {@link CloudExperimentsMetric} + * + * @public + */ + reportMetric: (metric: CloudExperimentsMetric) => void; +} + +/** + * The names of the metrics declared in Kibana. + * Valid keys are defined in {@link METRIC_NAMES}. When reporting a new metric, add the name to the list. + * + * @public + */ +export type CloudExperimentsMetricNames = keyof typeof METRIC_NAMES; + +/** + * Definition of the metric to report back to the A/B testing service to measure the conversions. + * + * @public + */ +export interface CloudExperimentsMetric { + /** + * The name of the metric {@link CloudExperimentsMetricNames} + */ + name: CloudExperimentsMetricNames; + /** + * Any optional data to enrich the context of the metric. Or if the conversion is based on a non-numeric value. + */ + meta?: Data; + /** + * The numeric value of the metric. Bear in mind that they are averaged by the underlying solution. + * Typical values to report here are time-to-action, number of panels in a loaded dashboard, and page load time. + */ + value?: number; +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/jest.config.js b/x-pack/plugins/cloud_integrations/cloud_experiments/jest.config.js new file mode 100644 index 0000000000000..a15dd88e1cb41 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/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/cloud_integrations/cloud_experiments'], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/cloud_integrations/cloud_experiments', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/cloud_integrations/cloud_experiments/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.json b/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.json new file mode 100755 index 0000000000000..412b16810c8bd --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.json @@ -0,0 +1,15 @@ +{ + "id": "cloudExperiments", + "version": "1.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "Kibana Core", + "githubTeam": "@elastic/kibana-core" + }, + "description": "Provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments.", + "server": true, + "ui": true, + "configPath": ["xpack", "cloud_integrations", "experiments"], + "requiredPlugins": [], + "optionalPlugins": ["usageCollection"] +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/public/index.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/public/index.ts new file mode 100755 index 0000000000000..b6704f09e5773 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/public/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. + */ + +import type { PluginInitializerContext } from '@kbn/core/public'; +import { CloudExperimentsPlugin } from './plugin'; + +export function plugin(core: PluginInitializerContext) { + return new CloudExperimentsPlugin(core); +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.mock.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.mock.ts new file mode 100644 index 0000000000000..d2bfb5b54213d --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.mock.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 { LDClient } from 'launchdarkly-js-client-sdk'; + +export function createLaunchDarklyClientMock(): jest.Mocked { + return { + waitForInitialization: jest.fn(), + variation: jest.fn(), + track: jest.fn(), + identify: jest.fn(), + flush: jest.fn(), + } as unknown as jest.Mocked; // Using casting because we only use these APIs. No need to declare everything. +} + +export const ldClientMock = createLaunchDarklyClientMock(); + +jest.doMock('launchdarkly-js-client-sdk', () => ({ + initialize: () => ldClientMock, +})); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts new file mode 100644 index 0000000000000..63dd98991d6c6 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts @@ -0,0 +1,274 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { coreMock } from '@kbn/core/public/mocks'; +import { ldClientMock } from './plugin.test.mock'; +import { CloudExperimentsPlugin } from './plugin'; +import { FEATURE_FLAG_NAMES } from '../common/constants'; + +describe('Cloud Experiments public plugin', () => { + jest.spyOn(console, 'debug').mockImplementation(); // silence console.debug logs + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('constructor', () => { + test('successfully creates a new plugin if provided an empty configuration', () => { + const initializerContext = coreMock.createPluginInitializerContext(); + // @ts-expect-error it's defined as readonly but the mock is not. + initializerContext.env.mode.dev = true; // ensure it's true + const plugin = new CloudExperimentsPlugin(initializerContext); + expect(plugin).toHaveProperty('setup'); + expect(plugin).toHaveProperty('start'); + expect(plugin).toHaveProperty('stop'); + expect(plugin).toHaveProperty('flagOverrides', undefined); + expect(plugin).toHaveProperty('launchDarklyClient', undefined); + }); + + test('fails if launch_darkly is not provided in the config and it is a non-dev environment', () => { + const initializerContext = coreMock.createPluginInitializerContext(); + // @ts-expect-error it's defined as readonly but the mock is not. + initializerContext.env.mode.dev = false; + expect(() => new CloudExperimentsPlugin(initializerContext)).toThrowError( + 'xpack.cloud_integrations.experiments.launch_darkly configuration should exist' + ); + }); + + test('it initializes the flagOverrides property', () => { + const initializerContext = coreMock.createPluginInitializerContext({ + flag_overrides: { my_flag: '1234' }, + }); + // @ts-expect-error it's defined as readonly but the mock is not. + initializerContext.env.mode.dev = true; // ensure it's true + const plugin = new CloudExperimentsPlugin(initializerContext); + expect(plugin).toHaveProperty('flagOverrides', { my_flag: '1234' }); + }); + }); + + describe('setup', () => { + let plugin: CloudExperimentsPlugin; + + beforeEach(() => { + const initializerContext = coreMock.createPluginInitializerContext({ + launch_darkly: { client_id: '1234' }, + flag_overrides: { my_flag: '1234' }, + }); + plugin = new CloudExperimentsPlugin(initializerContext); + }); + + test('returns the contract', () => { + const setupContract = plugin.setup(coreMock.createSetup()); + expect(setupContract).toStrictEqual( + expect.objectContaining({ + identifyUser: expect.any(Function), + }) + ); + }); + + describe('identifyUser', () => { + test('it skips creating the client if no client id provided in the config', () => { + const initializerContext = coreMock.createPluginInitializerContext({ + flag_overrides: { my_flag: '1234' }, + }); + const customPlugin = new CloudExperimentsPlugin(initializerContext); + const setupContract = customPlugin.setup(coreMock.createSetup()); + expect(customPlugin).toHaveProperty('launchDarklyClient', undefined); + setupContract.identifyUser('user-id', {}); + expect(customPlugin).toHaveProperty('launchDarklyClient', undefined); + }); + + test('it initializes the LaunchDarkly client', () => { + const setupContract = plugin.setup(coreMock.createSetup()); + expect(plugin).toHaveProperty('launchDarklyClient', undefined); + setupContract.identifyUser('user-id', {}); + expect(plugin).toHaveProperty('launchDarklyClient', ldClientMock); + expect(ldClientMock.identify).not.toHaveBeenCalled(); + }); + + test('it calls identify if the client already exists', () => { + const setupContract = plugin.setup(coreMock.createSetup()); + expect(plugin).toHaveProperty('launchDarklyClient', undefined); + setupContract.identifyUser('user-id', {}); + expect(plugin).toHaveProperty('launchDarklyClient', ldClientMock); + expect(ldClientMock.identify).not.toHaveBeenCalled(); + ldClientMock.identify.mockResolvedValue({}); // ensure it's a promise + setupContract.identifyUser('user-id', {}); + expect(ldClientMock.identify).toHaveBeenCalledTimes(1); + }); + + test('it handles identify rejections', async () => { + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + const setupContract = plugin.setup(coreMock.createSetup()); + expect(plugin).toHaveProperty('launchDarklyClient', undefined); + setupContract.identifyUser('user-id', {}); + expect(plugin).toHaveProperty('launchDarklyClient', ldClientMock); + expect(ldClientMock.identify).not.toHaveBeenCalled(); + const error = new Error('Something went terribly wrong'); + ldClientMock.identify.mockRejectedValue(error); + setupContract.identifyUser('user-id', {}); + expect(ldClientMock.identify).toHaveBeenCalledTimes(1); + await new Promise((resolve) => process.nextTick(resolve)); + expect(consoleWarnSpy).toHaveBeenCalledWith(error); + }); + }); + }); + + describe('start', () => { + let plugin: CloudExperimentsPlugin; + + const firstKnownFlag = Object.keys(FEATURE_FLAG_NAMES)[0] as keyof typeof FEATURE_FLAG_NAMES; + + beforeEach(() => { + const initializerContext = coreMock.createPluginInitializerContext({ + launch_darkly: { client_id: '1234' }, + flag_overrides: { [firstKnownFlag]: '1234' }, + }); + plugin = new CloudExperimentsPlugin(initializerContext); + }); + + test('returns the contract', () => { + plugin.setup(coreMock.createSetup()); + const startContract = plugin.start(coreMock.createStart()); + expect(startContract).toStrictEqual( + expect.objectContaining({ + getVariation: expect.any(Function), + reportMetric: expect.any(Function), + }) + ); + }); + + describe('getVariation', () => { + describe('with the user identified', () => { + beforeEach(() => { + const setupContract = plugin.setup(coreMock.createSetup()); + setupContract.identifyUser('user-id', {}); + }); + + test('uses the flag overrides to respond early', async () => { + const startContract = plugin.start(coreMock.createStart()); + await expect(startContract.getVariation(firstKnownFlag, 123)).resolves.toStrictEqual( + '1234' + ); + }); + + test('calls the client', async () => { + const startContract = plugin.start(coreMock.createStart()); + ldClientMock.variation.mockReturnValue('12345'); + await expect( + startContract.getVariation( + // @ts-expect-error We only allow existing flags in FEATURE_FLAG_NAMES + 'some-random-flag', + 123 + ) + ).resolves.toStrictEqual('12345'); + expect(ldClientMock.variation).toHaveBeenCalledWith( + undefined, // it couldn't find it in FEATURE_FLAG_NAMES + 123 + ); + }); + }); + + describe('with the user not identified', () => { + beforeEach(() => { + plugin.setup(coreMock.createSetup()); + }); + + test('uses the flag overrides to respond early', async () => { + const startContract = plugin.start(coreMock.createStart()); + await expect(startContract.getVariation(firstKnownFlag, 123)).resolves.toStrictEqual( + '1234' + ); + }); + + test('returns the default value without calling the client', async () => { + const startContract = plugin.start(coreMock.createStart()); + await expect( + startContract.getVariation( + // @ts-expect-error We only allow existing flags in FEATURE_FLAG_NAMES + 'some-random-flag', + 123 + ) + ).resolves.toStrictEqual(123); + expect(ldClientMock.variation).not.toHaveBeenCalled(); + }); + }); + }); + + describe('reportMetric', () => { + describe('with the user identified', () => { + beforeEach(() => { + const setupContract = plugin.setup(coreMock.createSetup()); + setupContract.identifyUser('user-id', {}); + }); + + test('calls the track API', () => { + const startContract = plugin.start(coreMock.createStart()); + startContract.reportMetric({ + // @ts-expect-error We only allow existing flags in METRIC_NAMES + name: 'my-flag', + meta: {}, + value: 1, + }); + expect(ldClientMock.track).toHaveBeenCalledWith( + undefined, // it couldn't find it in METRIC_NAMES + {}, + 1 + ); + }); + }); + + describe('with the user not identified', () => { + beforeEach(() => { + plugin.setup(coreMock.createSetup()); + }); + + test('calls the track API', () => { + const startContract = plugin.start(coreMock.createStart()); + startContract.reportMetric({ + // @ts-expect-error We only allow existing flags in METRIC_NAMES + name: 'my-flag', + meta: {}, + value: 1, + }); + expect(ldClientMock.track).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe('stop', () => { + let plugin: CloudExperimentsPlugin; + + beforeEach(() => { + const initializerContext = coreMock.createPluginInitializerContext({ + launch_darkly: { client_id: '1234' }, + flag_overrides: { my_flag: '1234' }, + }); + plugin = new CloudExperimentsPlugin(initializerContext); + const setupContract = plugin.setup(coreMock.createSetup()); + setupContract.identifyUser('user-id', {}); + plugin.start(coreMock.createStart()); + }); + + test('flushes the events on stop', () => { + ldClientMock.flush.mockResolvedValue(); + expect(() => plugin.stop()).not.toThrow(); + expect(ldClientMock.flush).toHaveBeenCalledTimes(1); + }); + + test('handles errors when flushing events', async () => { + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + const error = new Error('Something went terribly wrong'); + ldClientMock.flush.mockRejectedValue(error); + expect(() => plugin.stop()).not.toThrow(); + expect(ldClientMock.flush).toHaveBeenCalledTimes(1); + await new Promise((resolve) => process.nextTick(resolve)); + expect(consoleWarnSpy).toHaveBeenCalledWith(error); + }); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts new file mode 100755 index 0000000000000..6206aadc24c31 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import LaunchDarkly, { type LDClient } from 'launchdarkly-js-client-sdk'; +import { get, has } from 'lodash'; +import type { + CloudExperimentsFeatureFlagNames, + CloudExperimentsMetric, + CloudExperimentsPluginSetup, + CloudExperimentsPluginStart, +} from '../common'; +import { FEATURE_FLAG_NAMES, METRIC_NAMES } from '../common/constants'; + +/** + * Browser-side implementation of the Cloud Experiments plugin + */ +export class CloudExperimentsPlugin + implements Plugin +{ + private launchDarklyClient?: LDClient; + private readonly clientId?: string; + private readonly kibanaVersion: string; + private readonly flagOverrides?: Record; + private readonly isDev: boolean; + + /** Constructor of the plugin **/ + constructor(initializerContext: PluginInitializerContext) { + this.isDev = initializerContext.env.mode.dev; + this.kibanaVersion = initializerContext.env.packageInfo.version; + const config = initializerContext.config.get<{ + launch_darkly?: { client_id: string }; + flag_overrides?: Record; + }>(); + if (config.flag_overrides) { + this.flagOverrides = config.flag_overrides; + } + const ldConfig = config.launch_darkly; + if (!ldConfig && !initializerContext.env.mode.dev) { + // If the plugin is enabled, and it's in prod mode, launch_darkly must exist + // (config-schema should enforce it, but just in case). + throw new Error( + 'xpack.cloud_integrations.experiments.launch_darkly configuration should exist' + ); + } + if (ldConfig) { + this.clientId = ldConfig.client_id; + } + } + + /** + * Returns the contract {@link CloudExperimentsPluginSetup} + * @param core {@link CoreSetup} + */ + public setup(core: CoreSetup): CloudExperimentsPluginSetup { + return { + identifyUser: (userId, userMetadata) => { + if (!this.clientId) return; // Only applies in dev mode. + + if (!this.launchDarklyClient) { + // If the client has not been initialized, create it with the user data.. + this.launchDarklyClient = LaunchDarkly.initialize( + this.clientId, + { key: userId, custom: userMetadata }, + { application: { id: 'kibana-browser', version: this.kibanaVersion } } + ); + } else { + // Otherwise, call the `identify` method. + this.launchDarklyClient + .identify({ key: userId, custom: userMetadata }) + // eslint-disable-next-line no-console + .catch((err) => console.warn(err)); + } + }, + }; + } + + /** + * Returns the contract {@link CloudExperimentsPluginStart} + * @param core {@link CoreStart} + */ + public start(core: CoreStart): CloudExperimentsPluginStart { + return { + getVariation: this.getVariation, + reportMetric: this.reportMetric, + }; + } + + /** + * Cleans up and flush the sending queues. + */ + public stop() { + this.launchDarklyClient + ?.flush() + // eslint-disable-next-line no-console + .catch((err) => console.warn(err)); + } + + private getVariation = async ( + featureFlagName: CloudExperimentsFeatureFlagNames, + defaultValue: Data + ): Promise => { + const configKey = FEATURE_FLAG_NAMES[featureFlagName]; + // Apply overrides if they exist without asking LaunchDarkly. + if (this.flagOverrides && has(this.flagOverrides, configKey)) { + return get(this.flagOverrides, configKey, defaultValue) as Data; + } + if (!this.launchDarklyClient) return defaultValue; // Skip any action if no LD User is defined + await this.launchDarklyClient.waitForInitialization(); + return this.launchDarklyClient.variation(configKey, defaultValue); + }; + + private reportMetric = ({ name, meta, value }: CloudExperimentsMetric): void => { + const metricName = METRIC_NAMES[name]; + this.launchDarklyClient?.track(metricName, meta, value); + if (this.isDev) { + // eslint-disable-next-line no-console + console.debug(`Reported experimentation metric ${metricName}`, { + experimentationMetric: { name, meta, value }, + }); + } + }; +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/config.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/config.test.ts new file mode 100644 index 0000000000000..2e762ece1d8fe --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/config.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 { config } from './config'; + +describe('cloudExperiments config', () => { + describe.each([true, false])('when disabled (dev: %p)', (dev) => { + const ctx = { dev }; + test('should default to `enabled:false` and the rest empty', () => { + expect(config.schema.validate({}, ctx)).toStrictEqual({ enabled: false }); + }); + + test('it should allow any additional config', () => { + const cfg = { + enabled: false, + launch_darkly: { + sdk_key: 'sdk-1234', + client_id: '1234', + client_log_level: 'none', + }, + flag_overrides: { + 'my-plugin.my-feature-flag': 1234, + }, + }; + expect(config.schema.validate(cfg, ctx)).toStrictEqual(cfg); + }); + + test('it should allow any additional config (missing flag_overrides)', () => { + const cfg = { + enabled: false, + launch_darkly: { + sdk_key: 'sdk-1234', + client_id: '1234', + client_log_level: 'none', + }, + }; + expect(config.schema.validate(cfg, ctx)).toStrictEqual(cfg); + }); + + test('it should allow any additional config (missing launch_darkly)', () => { + const cfg = { + enabled: false, + flag_overrides: { + 'my-plugin.my-feature-flag': 1234, + }, + }; + expect(config.schema.validate(cfg, ctx)).toStrictEqual(cfg); + }); + }); + + describe('when enabled', () => { + describe('in dev mode', () => { + const ctx = { dev: true }; + test('in dev mode, it allows `launch_darkly` to be empty', () => { + expect( + config.schema.validate({ enabled: true, flag_overrides: { my_flag: 1 } }, ctx) + ).toStrictEqual({ + enabled: true, + flag_overrides: { my_flag: 1 }, + }); + }); + + test('in dev mode, it allows `launch_darkly` and `flag_overrides` to be empty', () => { + expect(config.schema.validate({ enabled: true }, ctx)).toStrictEqual({ enabled: true }); + }); + }); + + describe('in prod (non-dev mode)', () => { + const ctx = { dev: false }; + test('it enforces `launch_darkly` config if not in dev-mode', () => { + expect(() => + config.schema.validate({ enabled: true }, ctx) + ).toThrowErrorMatchingInlineSnapshot( + `"[launch_darkly.sdk_key]: expected value of type [string] but got [undefined]"` + ); + }); + + test('in prod mode, it allows `flag_overrides` to be empty', () => { + expect( + config.schema.validate( + { + enabled: true, + launch_darkly: { + sdk_key: 'sdk-1234', + client_id: '1234', + }, + }, + ctx + ) + ).toStrictEqual({ + enabled: true, + launch_darkly: { + sdk_key: 'sdk-1234', + client_id: '1234', + client_log_level: 'none', + }, + }); + }); + + test('in prod mode, it allows `flag_overrides` to be provided', () => { + expect( + config.schema.validate( + { + enabled: true, + launch_darkly: { + sdk_key: 'sdk-1234', + client_id: '1234', + }, + flag_overrides: { + my_flag: 123, + }, + }, + ctx + ) + ).toStrictEqual({ + enabled: true, + launch_darkly: { + sdk_key: 'sdk-1234', + client_id: '1234', + client_log_level: 'none', + }, + flag_overrides: { + my_flag: 123, + }, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/config.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/config.ts new file mode 100644 index 0000000000000..79b49bcb77509 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/config.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 { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from '@kbn/core/server'; + +const launchDarklySchema = schema.object({ + sdk_key: schema.string({ minLength: 1 }), + client_id: schema.string({ minLength: 1 }), + client_log_level: schema.oneOf( + [ + schema.literal('none'), + schema.literal('error'), + schema.literal('warn'), + schema.literal('info'), + schema.literal('debug'), + ], + { defaultValue: 'none' } + ), +}); + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + launch_darkly: schema.conditional( + schema.siblingRef('enabled'), + true, + schema.conditional( + schema.contextRef('dev'), + schema.literal(true), // this is still optional when running on dev because devs might use the `flag_overrides` + schema.maybe(launchDarklySchema), + launchDarklySchema + ), + schema.maybe(launchDarklySchema) + ), + flag_overrides: schema.maybe(schema.recordOf(schema.string(), schema.any())), +}); + +export type CloudExperimentsConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + launch_darkly: { + client_id: true, + }, + flag_overrides: true, + }, + schema: configSchema, +}; diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/index.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/index.ts new file mode 100755 index 0000000000000..6222c8108c0f5 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/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 type { PluginInitializerContext } from '@kbn/core/server'; +import { CloudExperimentsPlugin } from './plugin'; + +export { config } from './config'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new CloudExperimentsPlugin(initializerContext); +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.mock.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.mock.ts new file mode 100644 index 0000000000000..b76e629458e00 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.mock.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LDClient } from 'launchdarkly-node-server-sdk'; + +export function createLaunchDarklyClientMock(): jest.Mocked { + return { + waitForInitialization: jest.fn(), + variation: jest.fn(), + allFlagsState: jest.fn(), + track: jest.fn(), + identify: jest.fn(), + flush: jest.fn(), + } as unknown as jest.Mocked; // Using casting because we only use these APIs. No need to declare everything. +} + +export const ldClientMock = createLaunchDarklyClientMock(); + +jest.doMock('launchdarkly-node-server-sdk', () => ({ + init: () => ldClientMock, + basicLogger: jest.fn(), +})); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts new file mode 100644 index 0000000000000..1369f80779d7f --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts @@ -0,0 +1,250 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { coreMock } from '@kbn/core/server/mocks'; +import { ldClientMock } from './plugin.test.mock'; +import { CloudExperimentsPlugin } from './plugin'; +import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; +import { FEATURE_FLAG_NAMES } from '../common/constants'; + +describe('Cloud Experiments server plugin', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('constructor', () => { + test('successfully creates a new plugin if provided an empty configuration', () => { + const initializerContext = coreMock.createPluginInitializerContext(); + initializerContext.env.mode.dev = true; // ensure it's true + const plugin = new CloudExperimentsPlugin(initializerContext); + expect(plugin).toHaveProperty('setup'); + expect(plugin).toHaveProperty('start'); + expect(plugin).toHaveProperty('stop'); + expect(plugin).toHaveProperty('flagOverrides', undefined); + expect(plugin).toHaveProperty('launchDarklyClient', undefined); + }); + + test('fails if launch_darkly is not provided in the config and it is a non-dev environment', () => { + const initializerContext = coreMock.createPluginInitializerContext(); + initializerContext.env.mode.dev = false; + expect(() => new CloudExperimentsPlugin(initializerContext)).toThrowError( + 'xpack.cloud_integrations.experiments.launch_darkly configuration should exist' + ); + }); + + test('it initializes the LaunchDarkly client', () => { + const initializerContext = coreMock.createPluginInitializerContext({ + launch_darkly: { sdk_key: 'sdk-1234' }, + }); + ldClientMock.waitForInitialization.mockResolvedValue(ldClientMock); + const plugin = new CloudExperimentsPlugin(initializerContext); + expect(plugin).toHaveProperty('launchDarklyClient', ldClientMock); + }); + + test('it initializes the flagOverrides property', () => { + const initializerContext = coreMock.createPluginInitializerContext({ + flag_overrides: { my_flag: '1234' }, + }); + initializerContext.env.mode.dev = true; // ensure it's true + const plugin = new CloudExperimentsPlugin(initializerContext); + expect(plugin).toHaveProperty('flagOverrides', { my_flag: '1234' }); + }); + }); + + describe('setup', () => { + let plugin: CloudExperimentsPlugin; + + beforeEach(() => { + const initializerContext = coreMock.createPluginInitializerContext({ + launch_darkly: { sdk_key: 'sdk-1234' }, + flag_overrides: { my_flag: '1234' }, + }); + ldClientMock.waitForInitialization.mockResolvedValue(ldClientMock); + plugin = new CloudExperimentsPlugin(initializerContext); + }); + + test('returns the contract', () => { + const setupContract = plugin.setup(coreMock.createSetup(), {}); + expect(setupContract).toStrictEqual( + expect.objectContaining({ + identifyUser: expect.any(Function), + }) + ); + }); + + test('registers the usage collector when available', () => { + const usageCollection = usageCollectionPluginMock.createSetupContract(); + plugin.setup(coreMock.createSetup(), { usageCollection }); + expect(usageCollection.makeUsageCollector).toHaveBeenCalledTimes(1); + expect(usageCollection.registerCollector).toHaveBeenCalledTimes(1); + }); + + describe('identifyUser', () => { + test('sets launchDarklyUser and calls identify', () => { + expect(plugin).toHaveProperty('launchDarklyUser', undefined); + const setupContract = plugin.setup(coreMock.createSetup(), {}); + setupContract.identifyUser('user-id', {}); + const ldUser = { key: 'user-id', custom: {} }; + expect(plugin).toHaveProperty('launchDarklyUser', ldUser); + expect(ldClientMock.identify).toHaveBeenCalledWith(ldUser); + }); + }); + }); + + describe('start', () => { + let plugin: CloudExperimentsPlugin; + + const firstKnownFlag = Object.keys(FEATURE_FLAG_NAMES)[0] as keyof typeof FEATURE_FLAG_NAMES; + + beforeEach(() => { + const initializerContext = coreMock.createPluginInitializerContext({ + launch_darkly: { sdk_key: 'sdk-1234' }, + flag_overrides: { [firstKnownFlag]: '1234' }, + }); + ldClientMock.waitForInitialization.mockResolvedValue(ldClientMock); + plugin = new CloudExperimentsPlugin(initializerContext); + }); + + test('returns the contract', () => { + plugin.setup(coreMock.createSetup(), {}); + const startContract = plugin.start(coreMock.createStart()); + expect(startContract).toStrictEqual( + expect.objectContaining({ + getVariation: expect.any(Function), + reportMetric: expect.any(Function), + }) + ); + }); + + describe('getVariation', () => { + describe('with the user identified', () => { + beforeEach(() => { + const setupContract = plugin.setup(coreMock.createSetup(), {}); + setupContract.identifyUser('user-id', {}); + }); + + test('uses the flag overrides to respond early', async () => { + const startContract = plugin.start(coreMock.createStart()); + await expect(startContract.getVariation(firstKnownFlag, 123)).resolves.toStrictEqual( + '1234' + ); + }); + + test('calls the client', async () => { + const startContract = plugin.start(coreMock.createStart()); + ldClientMock.variation.mockResolvedValue('12345'); + await expect( + startContract.getVariation( + // @ts-expect-error We only allow existing flags in FEATURE_FLAG_NAMES + 'some-random-flag', + 123 + ) + ).resolves.toStrictEqual('12345'); + expect(ldClientMock.variation).toHaveBeenCalledWith( + undefined, // it couldn't find it in FEATURE_FLAG_NAMES + { key: 'user-id', custom: {} }, + 123 + ); + }); + }); + + describe('with the user not identified', () => { + beforeEach(() => { + plugin.setup(coreMock.createSetup(), {}); + }); + + test('uses the flag overrides to respond early', async () => { + const startContract = plugin.start(coreMock.createStart()); + await expect(startContract.getVariation(firstKnownFlag, 123)).resolves.toStrictEqual( + '1234' + ); + }); + + test('returns the default value without calling the client', async () => { + const startContract = plugin.start(coreMock.createStart()); + await expect( + startContract.getVariation( + // @ts-expect-error We only allow existing flags in FEATURE_FLAG_NAMES + 'some-random-flag', + 123 + ) + ).resolves.toStrictEqual(123); + expect(ldClientMock.variation).not.toHaveBeenCalled(); + }); + }); + }); + + describe('reportMetric', () => { + describe('with the user identified', () => { + beforeEach(() => { + const setupContract = plugin.setup(coreMock.createSetup(), {}); + setupContract.identifyUser('user-id', {}); + }); + + test('calls the track API', () => { + const startContract = plugin.start(coreMock.createStart()); + startContract.reportMetric({ + // @ts-expect-error We only allow existing flags in METRIC_NAMES + name: 'my-flag', + meta: {}, + value: 1, + }); + expect(ldClientMock.track).toHaveBeenCalledWith( + undefined, // it couldn't find it in METRIC_NAMES + { key: 'user-id', custom: {} }, + {}, + 1 + ); + }); + }); + + describe('with the user not identified', () => { + beforeEach(() => { + plugin.setup(coreMock.createSetup(), {}); + }); + + test('calls the track API', () => { + const startContract = plugin.start(coreMock.createStart()); + startContract.reportMetric({ + // @ts-expect-error We only allow existing flags in METRIC_NAMES + name: 'my-flag', + meta: {}, + value: 1, + }); + expect(ldClientMock.track).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe('stop', () => { + let plugin: CloudExperimentsPlugin; + + beforeEach(() => { + const initializerContext = coreMock.createPluginInitializerContext({ + launch_darkly: { sdk_key: 'sdk-1234' }, + flag_overrides: { my_flag: '1234' }, + }); + ldClientMock.waitForInitialization.mockResolvedValue(ldClientMock); + plugin = new CloudExperimentsPlugin(initializerContext); + plugin.setup(coreMock.createSetup(), {}); + plugin.start(coreMock.createStart()); + }); + + test('flushes the events', () => { + ldClientMock.flush.mockResolvedValue(); + expect(() => plugin.stop()).not.toThrow(); + expect(ldClientMock.flush).toHaveBeenCalledTimes(1); + }); + + test('handles errors when flushing events', () => { + ldClientMock.flush.mockRejectedValue(new Error('Something went terribly wrong')); + expect(() => plugin.stop()).not.toThrow(); + expect(ldClientMock.flush).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts new file mode 100755 index 0000000000000..2ffc0d0a464f5 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. 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, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '@kbn/core/server'; +import { get, has } from 'lodash'; +import LaunchDarkly, { type LDClient, type LDUser } from 'launchdarkly-node-server-sdk'; +import type { LogMeta } from '@kbn/logging'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import { registerUsageCollector } from './usage'; +import type { CloudExperimentsConfigType } from './config'; +import type { + CloudExperimentsFeatureFlagNames, + CloudExperimentsMetric, + CloudExperimentsPluginSetup, + CloudExperimentsPluginStart, +} from '../common'; +import { FEATURE_FLAG_NAMES, METRIC_NAMES } from '../common/constants'; + +interface CloudExperimentsPluginSetupDeps { + usageCollection?: UsageCollectionSetup; +} + +export class CloudExperimentsPlugin + implements + Plugin< + CloudExperimentsPluginSetup, + CloudExperimentsPluginStart, + CloudExperimentsPluginSetupDeps + > +{ + private readonly logger: Logger; + private readonly launchDarklyClient?: LDClient; + private readonly flagOverrides?: Record; + private launchDarklyUser: LDUser | undefined; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + const config = initializerContext.config.get(); + if (config.flag_overrides) { + this.flagOverrides = config.flag_overrides; + } + const ldConfig = config.launch_darkly; // If the plugin is enabled and no flag_overrides are provided (dev mode only), launch_darkly must exist + if (!ldConfig && !initializerContext.env.mode.dev) { + // If the plugin is enabled, and it's in prod mode, launch_darkly must exist + // (config-schema should enforce it, but just in case). + throw new Error( + 'xpack.cloud_integrations.experiments.launch_darkly configuration should exist' + ); + } + if (ldConfig) { + this.launchDarklyClient = LaunchDarkly.init(ldConfig.sdk_key, { + application: { id: `kibana-server`, version: initializerContext.env.packageInfo.version }, + logger: LaunchDarkly.basicLogger({ level: ldConfig.client_log_level }), + // For some reason, the stream API does not work in Kibana. `.waitForInitialization()` hangs forever (doesn't throw, neither logs any errors). + // Using polling for now until we resolve that issue. + // Relevant issue: https://github.com/launchdarkly/node-server-sdk/issues/132 + stream: false, + }); + this.launchDarklyClient.waitForInitialization().then( + () => this.logger.debug('LaunchDarkly is initialized!'), + (err) => this.logger.warn(`Error initializing LaunchDarkly: ${err}`) + ); + } + } + + public setup( + core: CoreSetup, + deps: CloudExperimentsPluginSetupDeps + ): CloudExperimentsPluginSetup { + if (deps.usageCollection) { + registerUsageCollector(deps.usageCollection, () => ({ + launchDarklyClient: this.launchDarklyClient, + launchDarklyUser: this.launchDarklyUser, + })); + } + + return { + identifyUser: (userId, userMetadata) => { + this.launchDarklyUser = { key: userId, custom: userMetadata }; + this.launchDarklyClient?.identify(this.launchDarklyUser!); + }, + }; + } + + public start(core: CoreStart) { + return { + getVariation: this.getVariation, + reportMetric: this.reportMetric, + }; + } + + public stop() { + this.launchDarklyClient?.flush().catch((err) => this.logger.error(err)); + } + + private getVariation = async ( + featureFlagName: CloudExperimentsFeatureFlagNames, + defaultValue: Data + ): Promise => { + const configKey = FEATURE_FLAG_NAMES[featureFlagName]; + // Apply overrides if they exist without asking LaunchDarkly. + if (this.flagOverrides && has(this.flagOverrides, configKey)) { + return get(this.flagOverrides, configKey, defaultValue) as Data; + } + if (!this.launchDarklyUser) return defaultValue; // Skip any action if no LD User is defined + await this.launchDarklyClient?.waitForInitialization(); + return await this.launchDarklyClient?.variation(configKey, this.launchDarklyUser, defaultValue); + }; + + private reportMetric = ({ name, meta, value }: CloudExperimentsMetric): void => { + const metricName = METRIC_NAMES[name]; + if (!this.launchDarklyUser) return; // Skip any action if no LD User is defined + this.launchDarklyClient?.track(metricName, this.launchDarklyUser, meta, value); + this.logger.debug<{ experimentationMetric: CloudExperimentsMetric } & LogMeta>( + `Reported experimentation metric ${metricName}`, + { + experimentationMetric: { name, meta, value }, + } + ); + }; +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/index.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/index.ts new file mode 100644 index 0000000000000..59f577ba20b70 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/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 { registerUsageCollector } from './register_usage_collector'; diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/register_usage_collector.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/register_usage_collector.test.ts new file mode 100644 index 0000000000000..176bbad4f6702 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/register_usage_collector.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 { + usageCollectionPluginMock, + createCollectorFetchContextMock, +} from '@kbn/usage-collection-plugin/server/mocks'; +import type { Collector } from '@kbn/usage-collection-plugin/server/mocks'; +import { + registerUsageCollector, + type LaunchDarklyEntitiesGetter, + type Usage, +} from './register_usage_collector'; +import { createLaunchDarklyClientMock } from '../plugin.test.mock'; + +describe('cloudExperiments usage collector', () => { + let collector: Collector; + const getLaunchDarklyEntitiesMock: jest.MockedFunction = jest + .fn() + .mockImplementation(() => ({})); + + beforeEach(() => { + const usageCollectionSetupMock = usageCollectionPluginMock.createSetupContract(); + registerUsageCollector(usageCollectionSetupMock, getLaunchDarklyEntitiesMock); + collector = usageCollectionSetupMock.registerCollector.mock + .calls[0][0] as unknown as Collector; + }); + + test('isReady should always be true', () => { + expect(collector.isReady()).toStrictEqual(true); + }); + + test('should return initialized false and empty values when the user and the client are not initialized', async () => { + await expect(collector.fetch(createCollectorFetchContextMock())).resolves.toStrictEqual({ + flagNames: [], + flags: {}, + initialized: false, + }); + }); + + test('should return initialized false and empty values when the user is not initialized', async () => { + getLaunchDarklyEntitiesMock.mockReturnValueOnce({ + launchDarklyClient: createLaunchDarklyClientMock(), + }); + await expect(collector.fetch(createCollectorFetchContextMock())).resolves.toStrictEqual({ + flagNames: [], + flags: {}, + initialized: false, + }); + }); + + test('should return initialized false and empty values when the client is not initialized', async () => { + getLaunchDarklyEntitiesMock.mockReturnValueOnce({ launchDarklyUser: { key: 'test' } }); + await expect(collector.fetch(createCollectorFetchContextMock())).resolves.toStrictEqual({ + flagNames: [], + flags: {}, + initialized: false, + }); + }); + + test('should return all the flags returned by the client', async () => { + const launchDarklyClient = createLaunchDarklyClientMock(); + getLaunchDarklyEntitiesMock.mockReturnValueOnce({ + launchDarklyClient, + launchDarklyUser: { key: 'test' }, + }); + + launchDarklyClient.allFlagsState.mockResolvedValueOnce({ + valid: true, + getFlagValue: jest.fn(), + getFlagReason: jest.fn(), + toJSON: jest.fn(), + allValues: jest.fn().mockReturnValueOnce({ + 'my-plugin.my-feature-flag': true, + 'my-plugin.my-other-feature-flag': 22, + }), + }); + + await expect(collector.fetch(createCollectorFetchContextMock())).resolves.toStrictEqual({ + flagNames: ['my-plugin.my-feature-flag', 'my-plugin.my-other-feature-flag'], + flags: { + 'my-plugin.my-feature-flag': true, + 'my-plugin.my-other-feature-flag': 22, + }, + initialized: true, + }); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/register_usage_collector.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/register_usage_collector.ts new file mode 100644 index 0000000000000..c9390915fd8c2 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/usage/register_usage_collector.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LDClient, LDUser } from 'launchdarkly-node-server-sdk'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; + +export interface Usage { + initialized: boolean; + flags: Record; + flagNames: string[]; +} + +export type LaunchDarklyEntitiesGetter = () => { + launchDarklyUser?: LDUser; + launchDarklyClient?: LDClient; +}; + +export function registerUsageCollector( + usageCollection: UsageCollectionSetup, + getLaunchDarklyEntities: LaunchDarklyEntitiesGetter +) { + usageCollection.registerCollector( + usageCollection.makeUsageCollector({ + type: 'cloudExperiments', + isReady: () => true, + schema: { + initialized: { + type: 'boolean', + _meta: { + description: + 'Whether the A/B testing client is correctly initialized (identify has been called)', + }, + }, + // We'll likely map "flags" as `flattened`, so "flagNames" helps out to discover the key names + flags: { + DYNAMIC_KEY: { + type: 'keyword', + _meta: { description: 'Flags received by the client' }, + }, + }, + flagNames: { + type: 'array', + items: { + type: 'keyword', + _meta: { + description: 'Names of the flags received by the client', + }, + }, + }, + }, + fetch: async () => { + const { launchDarklyUser, launchDarklyClient } = getLaunchDarklyEntities(); + if (!launchDarklyUser || !launchDarklyClient) + return { initialized: false, flagNames: [], flags: {} }; + // According to the docs, this method does not send analytics back to LaunchDarkly, so it does not provide false results + const flagsState = await launchDarklyClient.allFlagsState(launchDarklyUser); + const flags = flagsState.allValues(); + return { + initialized: flagsState.valid, + flags, + flagNames: Object.keys(flags), + }; + }, + }) + ); +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_experiments/tsconfig.json new file mode 100644 index 0000000000000..de8c2e7bb26a7 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + }, + "include": [ + ".storybook/**/*", + "common/**/*", + "public/**/*", + "server/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../../../src/core/tsconfig.json" }, + { "path": "../../../../src/plugins/usage_collection/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 623427df5c485..764cb49dae8d2 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -18,6 +18,7 @@ export const CSP_LATEST_FINDINGS_DATA_VIEW = 'logs-cloud_security_posture.findin export const FINDINGS_INDEX_NAME = 'logs-cloud_security_posture.findings'; export const FINDINGS_INDEX_PATTERN = 'logs-cloud_security_posture.findings-default*'; +export const FINDINGS_INDEX_DEFAULT_NS = 'logs-cloud_security_posture.findings-default'; export const LATEST_FINDINGS_INDEX_TEMPLATE_NAME = 'logs-cloud_security_posture.findings_latest'; export const LATEST_FINDINGS_INDEX_PATTERN = 'logs-cloud_security_posture.findings_latest-*'; @@ -29,6 +30,8 @@ export const BENCHMARK_SCORE_INDEX_PATTERN = 'logs-cloud_security_posture.scores export const BENCHMARK_SCORE_INDEX_DEFAULT_NS = 'logs-cloud_security_posture.scores-default'; export const CSP_INGEST_TIMESTAMP_PIPELINE = 'cloud_security_posture_add_ingest_timestamp_pipeline'; +export const CSP_LATEST_FINDINGS_INGEST_TIMESTAMP_PIPELINE = + 'cloud_security_posture_latest_index_add_ingest_timestamp_pipeline'; export const RULE_PASSED = `passed`; export const RULE_FAILED = `failed`; @@ -37,6 +40,7 @@ export const RULE_FAILED = `failed`; // activated via a simple code change in a single location. export const INTERNAL_FEATURE_FLAGS = { showManageRulesMock: false, + showFindingFlyoutEvidence: false, showFindingsGroupBy: true, } as const; diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts index 21c33c7f2603a..f4d8573beb4c1 100644 --- a/x-pack/plugins/cloud_security_posture/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/common/types.ts @@ -85,18 +85,7 @@ export interface CspRulesStatus { export type AgentPolicyStatus = Pick & { agents: number }; export interface Benchmark { - package_policy: Pick< - PackagePolicy, - | 'id' - | 'name' - | 'policy_id' - | 'namespace' - | 'package' - | 'updated_at' - | 'updated_by' - | 'created_at' - | 'created_by' - >; + package_policy: PackagePolicy; agent_policy: AgentPolicyStatus; rules: CspRulesStatus; } diff --git a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts index 43856c4e5b1ba..6d5dcd454a1c0 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { Truthy } from 'lodash'; import { CSP_RULE_SAVED_OBJECT_TYPE } from '../constants'; /** @@ -15,6 +16,8 @@ import { CSP_RULE_SAVED_OBJECT_TYPE } from '../constants'; export const isNonNullable = (v: T): v is NonNullable => v !== null && v !== undefined; +export const truthy = (value: T): value is Truthy => !!value; + export const extractErrorMessage = (e: unknown, defaultMessage = 'Unknown Error'): string => { if (e instanceof Error) return e.message; if (typeof e === 'string') return e; diff --git a/x-pack/plugins/cloud_security_posture/kibana.json b/x-pack/plugins/cloud_security_posture/kibana.json index 059de58acc21e..4660b97445ce0 100755 --- a/x-pack/plugins/cloud_security_posture/kibana.json +++ b/x-pack/plugins/cloud_security_posture/kibana.json @@ -22,5 +22,6 @@ "cloud", "licensing" ], - "requiredBundles": ["kibanaReact"] + "requiredBundles": ["kibanaReact"], + "optionalPlugins": ["usageCollection"] } diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts index 8033d16d476c8..597e037cb68d8 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -6,8 +6,44 @@ */ import { euiPaletteForStatus } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { CLOUDBEAT_EKS, CLOUDBEAT_VANILLA } from '../../common/constants'; const [success, warning, danger] = euiPaletteForStatus(3); export const statusColors = { success, warning, danger }; export const CSP_MOMENT_FORMAT = 'MMMM D, YYYY @ HH:mm:ss.SSS'; + +export type CloudPostureIntegrations = typeof cloudPostureIntegrations; + +export const cloudPostureIntegrations = { + kspm: { + policyTemplate: 'kspm', + name: i18n.translate('xpack.csp.kspmIntegration.integration.nameTitle', { + defaultMessage: 'Kubernetes Security Posture Management', + }), + shortName: i18n.translate('xpack.csp.kspmIntegration.integration.shortNameTitle', { + defaultMessage: 'KSPM', + }), + options: [ + { + type: CLOUDBEAT_VANILLA, + name: i18n.translate('xpack.csp.kspmIntegration.vanillaOption.nameTitle', { + defaultMessage: 'Unmanaged Kubernetes', + }), + benchmark: i18n.translate('xpack.csp.kspmIntegration.vanillaOption.benchmarkTitle', { + defaultMessage: 'CIS Kubernetes', + }), + }, + { + type: CLOUDBEAT_EKS, + name: i18n.translate('xpack.csp.kspmIntegration.eksOption.nameTitle', { + defaultMessage: 'EKS (Elastic Kubernetes Service)', + }), + benchmark: i18n.translate('xpack.csp.kspmIntegration.eksOption.benchmarkTitle', { + defaultMessage: 'CIS EKS', + }), + }, + ], + }, +} as const; diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts new file mode 100644 index 0000000000000..6cc02ac9aedc1 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.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 { PackagePolicy } from '@kbn/fleet-plugin/common'; +import { cloudPostureIntegrations, CloudPostureIntegrations } from '../constants'; + +const isPolicyTemplate = (name: unknown): name is keyof CloudPostureIntegrations => + typeof name === 'string' && name in cloudPostureIntegrations; + +export const getEnabledCspIntegrationDetails = (packageInfo?: PackagePolicy) => { + const enabledInput = packageInfo?.inputs.find((input) => input.enabled); + if (!enabledInput || !isPolicyTemplate(enabledInput.policy_template)) return null; + + const integration = cloudPostureIntegrations[enabledInput.policy_template]; + const enabledIntegrationOption = integration.options.find( + (option) => option.type === enabledInput.type + ); + + return { integration, enabledIntegrationOption }; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx b/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx index ab193ab6dc8be..0d838daa1e660 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx @@ -13,6 +13,10 @@ interface Props { type: 'passed' | 'failed'; } +// 'fail' / 'pass' are same chars length, but not same width size. +// 46px is used to make sure the badge is always the same width. +const BADGE_WIDTH = 46; + const getColor = (type: Props['type']): EuiBadgeProps['color'] => { if (type === 'passed') return 'success'; if (type === 'failed') return 'danger'; @@ -20,7 +24,7 @@ const getColor = (type: Props['type']): EuiBadgeProps['color'] => { }; export const CspEvaluationBadge = ({ type }: Props) => ( - + {type === 'failed' ? ( ) : ( diff --git a/x-pack/plugins/cloud_security_posture/public/components/csp_inline_description_list.tsx b/x-pack/plugins/cloud_security_posture/public/components/csp_inline_description_list.tsx new file mode 100644 index 0000000000000..7acb06d2eabb6 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/components/csp_inline_description_list.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiDescriptionList, useEuiTheme, type EuiDescriptionListProps } from '@elastic/eui'; + +const getModifiedTitlesListItems = (listItems: EuiDescriptionListProps['listItems']) => + listItems + ?.filter((item) => !!item?.title && !!item?.description) + .map((item) => ({ ...item, title: `${item.title}:` })); + +// eui size m is 12px which is too small, and next after it is base which is 16px which is too big +const fontSize = '1rem'; + +export const CspInlineDescriptionList = ({ + listItems, +}: { + listItems: EuiDescriptionListProps['listItems']; +}) => { + const { euiTheme } = useEuiTheme(); + const modifiedTitlesListItems = getModifiedTitlesListItems(listItems); + + return ( + + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/custom_assets_extension.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/custom_assets_extension.tsx index 584a859baec04..d281cced59cab 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/custom_assets_extension.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/custom_assets_extension.tsx @@ -37,7 +37,7 @@ export const CspCustomAssetsExtension = () => { name: cloudPosturePages.rules.name, url: application.getUrlForApp(SECURITY_APP_NAME, { path: cloudPosturePages.benchmarks.path }), description: i18n.translate('xpack.csp.createPackagePolicy.customAssetsTab.rulesViewLabel', { - defaultMessage: 'Manage CSP Rules ', + defaultMessage: 'View CSP Rules ', }), }, ]; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/deployment_type_select.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/deployment_type_select.tsx index 3085f9d83ea86..b01b5073a0e1b 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/deployment_type_select.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/deployment_type_select.tsx @@ -16,7 +16,7 @@ import { EuiDescribedFormGroup, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; +import { cloudPostureIntegrations } from '../../common/constants'; import { CLOUDBEAT_EKS, CLOUDBEAT_VANILLA } from '../../../common/constants'; export type InputType = typeof CLOUDBEAT_EKS | typeof CLOUDBEAT_VANILLA; @@ -27,22 +27,8 @@ interface Props { isDisabled?: boolean; } -const kubeDeployOptions: Array> = [ - { - value: CLOUDBEAT_VANILLA, - label: i18n.translate( - 'xpack.csp.createPackagePolicy.stepConfigure.integrationSettingsSection.vanillaKubernetesDeploymentOption', - { defaultMessage: 'Unmanaged Kubernetes' } - ), - }, - { - value: CLOUDBEAT_EKS, - label: i18n.translate( - 'xpack.csp.createPackagePolicy.stepConfigure.integrationSettingsSection.eksKubernetesDeploymentOption', - { defaultMessage: 'EKS (Elastic Kubernetes Service)' } - ), - }, -]; +const kubeDeployOptions: Array> = + cloudPostureIntegrations.kspm.options.map((o) => ({ value: o.type, label: o.name })); const KubernetesDeploymentFieldLabel = () => ( ', () => { expect(screen.getByText(item.package_policy.name)).toBeInTheDocument(); }); - it('renders benchmark name', () => { - const item = createCspBenchmarkIntegrationFixture(); - const benchmarks = [item]; - - render( - - - - ); - - expect(screen.getByText(item.package_policy.package!.title)).toBeInTheDocument(); - }); - it('renders agent policy name', () => { const agentPolicy = { id: chance.guid(), diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx index 5fdd845359cfb..df1a058de11b6 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx @@ -11,17 +11,19 @@ import { type EuiBasicTableProps, type Pagination, type CriteriaWithPagination, + EuiLink, } from '@elastic/eui'; import React from 'react'; -import { Link, useHistory, generatePath } from 'react-router-dom'; +import { generatePath } from 'react-router-dom'; import { pagePathGetters } from '@kbn/fleet-plugin/public'; -import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { TimestampTableCell } from '../../components/timestamp_table_cell'; import type { Benchmark } from '../../../common/types'; import { useKibana } from '../../common/hooks/use_kibana'; import { cloudPosturePages } from '../../common/navigation/constants'; import * as TEST_SUBJ from './test_subjects'; +import { getEnabledCspIntegrationDetails } from '../../common/utils/get_enabled_csp_integration_details'; interface BenchmarksTableProps extends Pick, 'loading' | 'error' | 'noItemsMessage' | 'sorting'>, @@ -32,71 +34,89 @@ interface BenchmarksTableProps } const AgentPolicyButtonLink = ({ name, id: policyId }: { name: string; id: string }) => { - const { http, application } = useKibana().services; + const { http } = useKibana().services; const [fleetBase, path] = pagePathGetters.policy_details({ policyId }); + + return {name}; +}; + +const IntegrationButtonLink = ({ + packageName, + policyId, + packagePolicyId, +}: { + packageName: string; + packagePolicyId: string; + policyId: string; +}) => { + const { application } = useKibana().services; + return ( - { - e.stopPropagation(); - e.preventDefault(); - application.navigateToApp('fleet', { path }); - }} + - {name} - + {packageName} + ); }; const BENCHMARKS_TABLE_COLUMNS: Array> = [ { field: 'package_policy.name', - name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.integrationColumnTitle', { - defaultMessage: 'Integration', + name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.integrationNameColumnTitle', { + defaultMessage: 'Integration Name', }), render: (packageName, benchmark) => ( - { - e.stopPropagation(); - }} - > - {packageName} - + ), truncateText: true, sortable: true, - 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.INTEGRATION, + 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.INTEGRATION_NAME, }, { - field: 'rules', - name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.activeRulesColumnTitle', { - defaultMessage: 'Active Rules', + field: 'rules.enabled', + name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.rulesColumnTitle', { + defaultMessage: 'Rules', }), truncateText: true, - render: ({ enabled, all }: Benchmark['rules']) => ( - - ), - 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.ACTIVE_RULES, + 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.RULES, }, { - field: 'package_policy.package.title', - name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.integrationTypeColumnTitle', { - defaultMessage: 'Integration Type', + field: 'package_policy', + name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.integrationColumnTitle', { + defaultMessage: 'Integration', + }), + dataType: 'string', + truncateText: true, + sortable: true, + 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.INTEGRATION, + render: (field: PackagePolicy) => { + const enabledIntegration = getEnabledCspIntegrationDetails(field); + return enabledIntegration?.integration?.shortName || ' '; + }, + }, + { + field: 'package_policy', + name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.deploymentTypeColumnTitle', { + defaultMessage: 'Deployment Type', }), dataType: 'string', truncateText: true, sortable: true, - 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.INTEGRATION_TYPE, + 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.DEPLOYMENT_TYPE, + render: (field: PackagePolicy) => { + const enabledIntegration = getEnabledCspIntegrationDetails(field); + return enabledIntegration?.enabledIntegrationOption?.name || ' '; + }, }, { field: 'agent_policy.name', @@ -154,18 +174,6 @@ export const BenchmarksTable = ({ sorting, ...rest }: BenchmarksTableProps) => { - const history = useHistory(); - - const getRowProps: EuiBasicTableProps['rowProps'] = (benchmark) => ({ - onClick: () => - history.push( - generatePath(cloudPosturePages.rules.path, { - packagePolicyId: benchmark.package_policy.id, - policyId: benchmark.package_policy.policy_id, - }) - ), - }); - const pagination: Pagination = { pageIndex: Math.max(pageIndex - 1, 0), pageSize, @@ -181,7 +189,6 @@ export const BenchmarksTable = ({ data-test-subj={rest['data-test-subj']} items={benchmarks} columns={BENCHMARKS_TABLE_COLUMNS} - rowProps={getRowProps} itemId={(item) => [item.agent_policy.id, item.package_policy.id].join('/')} pagination={pagination} onChange={onChange} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/test_subjects.ts index 3680c92f5d42b..d1d27325f9f88 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/test_subjects.ts @@ -9,9 +9,10 @@ export const BENCHMARKS_PAGE_HEADER = 'benchmarks-page-header'; export const BENCHMARKS_TABLE_DATA_TEST_SUBJ = 'csp_benchmarks_table'; export const ADD_INTEGRATION_TEST_SUBJ = 'csp_add_integration'; export const BENCHMARKS_TABLE_COLUMNS = { + INTEGRATION_NAME: 'benchmarks-table-column-integration-name', + DEPLOYMENT_TYPE: 'benchmarks-table-column-deployment-type', + RULES: 'benchmarks-table-column-rules', INTEGRATION: 'benchmarks-table-column-integration', - ACTIVE_RULES: 'benchmarks-table-column-active-rules', - INTEGRATION_TYPE: 'benchmarks-table-column-integration_type', AGENT_POLICY: 'benchmarks-table-column-agent-policy', NUMBER_OF_AGENTS: 'benchmarks-table-column-number-of-agents', CREATED_BY: 'benchmarks-table-column-created-by', diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx index 5b5969a3424a2..5dba83b9019b8 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx @@ -18,8 +18,12 @@ import React, { useMemo } from 'react'; import moment from 'moment'; import type { EuiDescriptionListProps, EuiAccordionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { truthy } from '../../../../common/utils/helpers'; import { CSP_MOMENT_FORMAT } from '../../../common/constants'; -import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../../../common/constants'; +import { + INTERNAL_FEATURE_FLAGS, + LATEST_FINDINGS_INDEX_DEFAULT_NS, +} from '../../../../common/constants'; import { useLatestFindingsDataView } from '../../../common/api/use_latest_findings_data_view'; import { useKibana } from '../../../common/hooks/use_kibana'; import { CspFinding } from '../../../../common/schemas/csp_finding'; @@ -53,6 +57,12 @@ const getDetailsList = (data: CspFinding, discoverIndexLink: string | undefined) }), description: moment(data['@timestamp']).format(CSP_MOMENT_FORMAT), }, + { + title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.resourceIdTitle', { + defaultMessage: 'Resource ID', + }), + description: data.resource.id, + }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.resourceNameTitle', { defaultMessage: 'Resource Name', @@ -135,7 +145,7 @@ const getEvidenceList = ({ result }: CspFinding) => }), description: {JSON.stringify(result.evidence, null, 2)}, }, - ].filter(Boolean) as EuiDescriptionListProps['listItems']; + ].filter(truthy); export const OverviewTab = ({ data }: { data: CspFinding }) => { const { @@ -152,33 +162,34 @@ export const OverviewTab = ({ data }: { data: CspFinding }) => { ); const accordions: Accordion[] = useMemo( - () => [ - { - initialIsOpen: true, - title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle', { - defaultMessage: 'Details', - }), - id: 'detailsAccordion', - listItems: getDetailsList(data, discoverIndexLink), - }, - { - initialIsOpen: true, - title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle', { - defaultMessage: 'Remediation', - }), - id: 'remediationAccordion', - listItems: getRemediationList(data.rule), - }, - { - initialIsOpen: false, - title: i18n.translate( - 'xpack.csp.findings.findingsFlyout.overviewTab.evidenceSourcesTitle', - { defaultMessage: 'Evidence' } - ), - id: 'evidenceAccordion', - listItems: getEvidenceList(data), - }, - ], + () => + [ + { + initialIsOpen: true, + title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle', { + defaultMessage: 'Details', + }), + id: 'detailsAccordion', + listItems: getDetailsList(data, discoverIndexLink), + }, + { + initialIsOpen: true, + title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle', { + defaultMessage: 'Remediation', + }), + id: 'remediationAccordion', + listItems: getRemediationList(data.rule), + }, + INTERNAL_FEATURE_FLAGS.showFindingFlyoutEvidence && { + initialIsOpen: false, + title: i18n.translate( + 'xpack.csp.findings.findingsFlyout.overviewTab.evidenceSourcesTitle', + { defaultMessage: 'Evidence' } + ), + id: 'evidenceAccordion', + listItems: getEvidenceList(data), + }, + ].filter(truthy), [data, discoverIndexLink] ); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx index 47c5128f919dc..3294ee8ec4965 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx @@ -6,8 +6,9 @@ */ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiBottomBar, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import type { Evaluation } from '../../../../common/types'; import { CloudPosturePageTitle } from '../../../components/cloud_posture_page_title'; import type { FindingsBaseProps } from '../types'; import { FindingsTable } from './latest_findings_table'; @@ -80,6 +81,19 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { [findingsGroupByNone.data?.total, urlQuery.pageIndex, urlQuery.pageSize] ); + const handleDistributionClick = (evaluation: Evaluation) => { + setUrlQuery({ + pageIndex: 0, + filters: getFilters({ + filters: urlQuery.filters, + dataView, + field: 'result.evaluation', + value: evaluation, + negate: false, + }), + }); + }; + return (
{ }} loading={findingsGroupByNone.isFetching} /> - + + + + + + {!error && } + + {error && } {!error && ( <> - {findingsGroupByNone.isSuccess && !!findingsGroupByNone.data.page.length && ( ({ - id: chance.word(), cluster_id: chance.guid(), + id: chance.word(), result: { expected: { source: {}, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx index 9eda791f1c3f0..78613d56a3fd1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx @@ -65,6 +65,7 @@ const FindingsTableComponent = ({ createColumnWithFilters(baseFindingsColumns['resource.sub_type'], { onAddFilter }), createColumnWithFilters(baseFindingsColumns['resource.name'], { onAddFilter }), createColumnWithFilters(baseFindingsColumns['rule.name'], { onAddFilter }), + createColumnWithFilters(baseFindingsColumns['rule.benchmark.name'], { onAddFilter }), baseFindingsColumns['rule.section'], baseFindingsColumns['rule.tags'], createColumnWithFilters(baseFindingsColumns.cluster_id, { onAddFilter }), diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx index c90b921c26efb..e9ce59fe9f0c0 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx @@ -6,8 +6,9 @@ */ import React from 'react'; import { Route, Switch } from 'react-router-dom'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import type { Evaluation } from '../../../../common/types'; import { CloudPosturePageTitle } from '../../../components/cloud_posture_page_title'; import { FindingsSearchBar } from '../layout/findings_search_bar'; import * as TEST_SUBJECTS from '../test_subjects'; @@ -80,6 +81,19 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { const error = findingsGroupByResource.error || baseEsQuery.error; + const handleDistributionClick = (evaluation: Evaluation) => { + setUrlQuery({ + pageIndex: 0, + filters: getFilters({ + filters: urlQuery.filters, + dataView, + field: 'result.evaluation', + value: evaluation, + negate: false, + }), + }); + }; + return (
{ }} loading={findingsGroupByResource.isFetching} /> - - + + + + } /> - } - /> - + + + + {!error && } + + {error && } {!error && ( <> - {findingsGroupByResource.isSuccess && !!findingsGroupByResource.data.page.length && ( { const total = chance.integer() + count + 1; const normalized = count / total; - const [resourceName, resourceSubtype, ...cisSections] = chance.unique(chance.word, 4); + const [resourceName, resourceSubtype, ruleBenchmarkName, ...cisSections] = chance.unique( + chance.word, + 5 + ); return { cluster_id: chance.guid(), @@ -29,6 +32,7 @@ const getFakeFindingsByResource = (): FindingsByResourcePage => { 'resource.name': resourceName, 'resource.sub_type': resourceSubtype, 'rule.section': cisSections, + 'rule.benchmark.name': ruleBenchmarkName, failed_findings: { count, normalized, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx index 497fee1e0b002..18ddb34256df9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx @@ -19,11 +19,14 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import numeral from '@elastic/numeral'; import { Link, generatePath } from 'react-router-dom'; -import { ColumnNameWithTooltip } from '../../../components/column_name_with_tooltip'; import * as TEST_SUBJECTS from '../test_subjects'; import type { FindingsByResourcePage } from './use_findings_by_resource'; import { findingsNavigation } from '../../../common/navigation/constants'; -import { createColumnWithFilters, type OnAddFilter } from '../layout/findings_layout'; +import { + createColumnWithFilters, + type OnAddFilter, + baseFindingsColumns, +} from '../layout/findings_layout'; export const formatNumber = (value: number) => value < 1000 ? value : numeral(value).format('0.0a'); @@ -62,6 +65,7 @@ const FindingsByResourceTableComponent = ({ findingsByResourceColumns.resource_id, createColumnWithFilters(findingsByResourceColumns['resource.sub_type'], { onAddFilter }), createColumnWithFilters(findingsByResourceColumns['resource.name'], { onAddFilter }), + createColumnWithFilters(findingsByResourceColumns['rule.benchmark.name'], { onAddFilter }), findingsByResourceColumns['rule.section'], createColumnWithFilters(findingsByResourceColumns.cluster_id, { onAddFilter }), findingsByResourceColumns.failed_findings, @@ -100,45 +104,21 @@ const FindingsByResourceTableComponent = ({ const baseColumns: Array> = [ { + ...baseFindingsColumns['resource.id'], field: 'resource_id', - name: ( - - ), render: (resourceId: FindingsByResourcePage['resource_id']) => ( - + {resourceId} ), }, - { - field: 'resource.sub_type', - truncateText: true, - name: ( - - ), - }, - { - field: 'resource.name', - truncateText: true, - name: ( - - ), - }, + baseFindingsColumns['resource.sub_type'], + baseFindingsColumns['resource.name'], + baseFindingsColumns['rule.benchmark.name'], { field: 'rule.section', truncateText: true, @@ -148,24 +128,16 @@ const baseColumns: Array> = defaultMessage="CIS Sections" /> ), - render: (sections: string[]) => sections.join(', '), - }, - { - field: 'cluster_id', - name: ( - - ), - truncateText: true, + render: (sections: string[]) => { + const items = sections.join(', '); + return ( + + <>{items} + + ); + }, }, + baseFindingsColumns.cluster_id, { field: 'failed_findings', width: '150px', diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx index 89344b13a4f8a..75997efaf6294 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx @@ -5,11 +5,18 @@ * 2.0. */ import React from 'react'; -import { EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; +import { + EuiSpacer, + EuiButtonEmpty, + EuiPageHeader, + type EuiDescriptionListProps, +} from '@elastic/eui'; import { Link, useParams } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { generatePath } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import { CspInlineDescriptionList } from '../../../../components/csp_inline_description_list'; +import type { Evaluation } from '../../../../../common/types'; import { CspFinding } from '../../../../../common/schemas/csp_finding'; import { CloudPosturePageTitle } from '../../../../components/cloud_posture_page_title'; import * as TEST_SUBJECTS from '../../test_subjects'; @@ -53,6 +60,32 @@ const BackToResourcesButton = () => ( ); +const getResourceFindingSharedValues = (sharedValues: { + resourceId: string; + resourceSubType: string; + resourceName: string; + clusterId: string; +}): EuiDescriptionListProps['listItems'] => [ + { + title: i18n.translate('xpack.csp.findings.resourceFindingsSharedValues.resourceTypeTitle', { + defaultMessage: 'Resource Type', + }), + description: sharedValues.resourceSubType, + }, + { + title: i18n.translate('xpack.csp.findings.resourceFindingsSharedValues.resourceIdTitle', { + defaultMessage: 'Resource ID', + }), + description: sharedValues.resourceId, + }, + { + title: i18n.translate('xpack.csp.findings.resourceFindingsSharedValues.clusterIdTitle', { + defaultMessage: 'Cluster ID', + }), + description: sharedValues.clusterId, + }, +]; + export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const params = useParams<{ resourceId: string }>(); const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); @@ -83,6 +116,19 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const error = resourceFindings.error || baseEsQuery.error; + const handleDistributionClick = (evaluation: Evaluation) => { + setUrlQuery({ + pageIndex: 0, + filters: getFilters({ + filters: urlQuery.filters, + dataView, + field: 'result.evaluation', + value: evaluation, + negate: false, + }), + }); + }; + return (
{ title={i18n.translate( 'xpack.csp.findings.resourceFindings.resourceFindingsPageTitle', { - defaultMessage: '{resourceId} - Findings', - values: { resourceId: params.resourceId }, + defaultMessage: '{resourceName} - Findings', + values: { resourceName: resourceFindings.data?.resourceName }, } )} /> } /> + + ) + } + /> {error && } {!error && ( @@ -115,6 +175,7 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { {resourceFindings.isSuccess && !!resourceFindings.data.page.length && ( [ getExpandColumn({ onClick: setSelectedFinding }), - baseFindingsColumns['resource.id'], createColumnWithFilters(baseFindingsColumns['result.evaluation'], { onAddFilter }), - createColumnWithFilters( - { ...baseFindingsColumns['resource.sub_type'], sortable: false }, - { onAddFilter } - ), - createColumnWithFilters( - { ...baseFindingsColumns['resource.name'], sortable: false }, - { onAddFilter } - ), createColumnWithFilters(baseFindingsColumns['rule.name'], { onAddFilter }), + createColumnWithFilters(baseFindingsColumns['rule.benchmark.name'], { onAddFilter }), baseFindingsColumns['rule.section'], baseFindingsColumns['rule.tags'], - createColumnWithFilters(baseFindingsColumns.cluster_id, { onAddFilter }), baseFindingsColumns['@timestamp'], ], [onAddFilter] ); + if (!loading && !items.length) return ( ; -type ResourceFindingsResponse = IKibanaSearchResponse>; +type ResourceFindingsResponse = IKibanaSearchResponse< + estypes.SearchResponse +>; -interface Aggs { - count: estypes.AggregationsMultiBucketAggregateBase; -} +export type ResourceFindingsResponseAggs = Record< + 'count' | 'clusterId' | 'resourceSubType' | 'resourceName', + estypes.AggregationsMultiBucketAggregateBase +>; const getResourceFindingsQuery = ({ query, @@ -60,7 +63,18 @@ const getResourceFindingsQuery = ({ }, sort: [{ [sort.field]: sort.direction }], pit: { id: pitId }, - aggs: getFindingsCountAggQuery(), + aggs: { + ...getFindingsCountAggQuery(), + clusterId: { + terms: { field: 'cluster_id' }, + }, + resourceSubType: { + terms: { field: 'resource.sub_type' }, + }, + resourceName: { + terms: { field: 'resource.name' }, + }, + }, }, ignore_unavailable: false, }); @@ -90,13 +104,18 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { }: ResourceFindingsResponse) => { if (!aggregations) throw new Error('expected aggregations to exists'); - if (!Array.isArray(aggregations?.count.buckets)) - throw new Error('expected buckets to be an array'); + assertNonEmptyArray(aggregations.count.buckets); + assertNonEmptyArray(aggregations.clusterId.buckets); + assertNonEmptyArray(aggregations.resourceSubType.buckets); + assertNonEmptyArray(aggregations.resourceName.buckets); return { page: hits.hits.map((hit) => hit._source!), total: number.is(hits.total) ? hits.total : 0, count: getAggregationCount(aggregations.count.buckets), + clusterId: getFirstBucketKey(aggregations.clusterId.buckets), + resourceSubType: getFirstBucketKey(aggregations.resourceSubType.buckets), + resourceName: getFirstBucketKey(aggregations.resourceName.buckets), newPitId: newPitId!, }; }, @@ -110,3 +129,12 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { } ); }; + +function assertNonEmptyArray(arr: unknown): asserts arr is T[] { + if (!Array.isArray(arr) || arr.length === 0) { + throw new Error('expected a non empty array'); + } +} + +const getFirstBucketKey = (buckets: estypes.AggregationsStringRareTermsBucketKeys[]) => + buckets[0].key; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/use_findings_by_resource.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/use_findings_by_resource.ts index 6def567d64ddd..0b940b4e1e9b2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/use_findings_by_resource.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/use_findings_by_resource.ts @@ -52,6 +52,7 @@ export interface FindingsByResourcePage { cluster_id: string; 'resource.name': string; 'resource.sub_type': string; + 'rule.benchmark.name': string; 'rule.section': string[]; } @@ -66,6 +67,7 @@ interface FindingsAggBucket extends estypes.AggregationsStringRareTermsBucketKey name: estypes.AggregationsMultiBucketAggregateBase; subtype: estypes.AggregationsMultiBucketAggregateBase; cluster_id: estypes.AggregationsMultiBucketAggregateBase; + benchmarkName: estypes.AggregationsMultiBucketAggregateBase; cis_sections: estypes.AggregationsMultiBucketAggregateBase; } @@ -91,6 +93,9 @@ export const getFindingsByResourceAggQuery = ({ subtype: { terms: { field: 'resource.sub_type', size: 1 }, }, + benchmarkName: { + terms: { field: 'rule.benchmark.name' }, + }, cis_sections: { terms: { field: 'rule.section' }, }, @@ -172,10 +177,12 @@ export const useFindingsByResource = (options: UseFindingsByResourceOptions) => const createFindingsByResource = (resource: FindingsAggBucket): FindingsByResourcePage => { if ( + !Array.isArray(resource.benchmarkName.buckets) || !Array.isArray(resource.cis_sections.buckets) || !Array.isArray(resource.name.buckets) || !Array.isArray(resource.subtype.buckets) || !Array.isArray(resource.cluster_id.buckets) || + !resource.benchmarkName.buckets.length || !resource.cis_sections.buckets.length || !resource.name.buckets.length || !resource.subtype.buckets.length || @@ -189,6 +196,7 @@ const createFindingsByResource = (resource: FindingsAggBucket): FindingsByResour ['resource.sub_type']: resource.subtype.buckets[0]?.key, cluster_id: resource.cluster_id.buckets[0]?.key, ['rule.section']: resource.cis_sections.buckets.map((v) => v.key), + ['rule.benchmark.name']: resource.benchmarkName.buckets[0]?.key, failed_findings: { count: resource.failed_findings.doc_count, normalized: diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/layout/findings_distribution_bar.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/layout/findings_distribution_bar.tsx index fd4b0351e9da0..f293b82341a61 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/layout/findings_distribution_bar.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/layout/findings_distribution_bar.tsx @@ -18,11 +18,14 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import numeral from '@elastic/numeral'; +import { RULE_FAILED, RULE_PASSED } from '../../../../common/constants'; +import type { Evaluation } from '../../../../common/types'; interface Props { total: number; passed: number; failed: number; + distributionOnClick: (evaluation: Evaluation) => void; pageStart: number; pageEnd: number; type: string; @@ -102,7 +105,11 @@ const CurrentPageOfTotal = ({ ); -const DistributionBar: React.FC> = ({ passed, failed }) => { +const DistributionBar: React.FC> = ({ + passed, + failed, + distributionOnClick, +}) => { const { euiTheme } = useEuiTheme(); return ( @@ -113,14 +120,35 @@ const DistributionBar: React.FC> = ({ passe background: ${euiTheme.colors.subduedText}; `} > - - + { + distributionOnClick(RULE_PASSED); + }} + /> + { + distributionOnClick(RULE_FAILED); + }} + /> ); }; -const DistributionBarPart = ({ value, color }: { value: number; color: string }) => ( -
void; +}) => ( + +
+ + + +
); })}
diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts index 58f6d2b01afa6..b9af9f4ec0331 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts @@ -11,7 +11,7 @@ import { useEuiTheme } from '../../../hooks'; type TTYPlayerLineMarkerType = 'output' | 'data_limited'; -export const useStyles = () => { +export const useStyles = (progress: number) => { const { euiTheme, euiVars } = useEuiTheme(); const cached = useMemo(() => { const { border } = euiTheme; @@ -23,6 +23,12 @@ export const useStyles = () => { width: '100%', }; + const markerWrapper: CSSObject = { + position: 'absolute', + top: 0, + lineHeight: 0, + }; + const getMarkerBackgroundColor = (type: TTYPlayerLineMarkerType, selected: boolean) => { if (type === 'data_limited') { return euiVars.terminalOutputMarkerWarning; @@ -36,7 +42,6 @@ export const useStyles = () => { const marker = (type: TTYPlayerLineMarkerType, selected: boolean): CSSObject => ({ fontSize: 0, overflow: 'hidden', - position: 'absolute', padding: 0, width: 3, height: 12, @@ -44,10 +49,8 @@ export const useStyles = () => { border: `${border.width.thick} solid ${euiVars.terminalOutputBackground}`, borderRadius: border.radius.small, boxSizing: 'content-box', - top: 0, - pointerEvents: 'none', marginLeft: '-3.5px', - transition: 'left .5s ease-in-out .3s', + transition: 'left .5s ease-in-out', }); const playHeadThumb: CSSObject = { @@ -82,6 +85,9 @@ export const useStyles = () => { "input[type='range']::-moz-range-thumb": customThumb, '.euiRangeHighlight__progress': { backgroundColor: euiVars.euiColorVis0_behindText, + width: progress + '%', + borderBottomRightRadius: 0, + borderTopRightRadius: 0, }, '.euiRangeSlider:focus ~ .euiRangeHighlight .euiRangeHighlight__progress': { backgroundColor: euiVars.euiColorVis0_behindText, @@ -93,9 +99,10 @@ export const useStyles = () => { }, }; - const playHead = (type: TTYPlayerLineMarkerType): CSSObject => ({ + const playHead = (type?: TTYPlayerLineMarkerType): CSSObject => ({ ...playHeadThumb, position: 'absolute', + left: progress + '%', top: 16, fill: type === 'data_limited' @@ -105,11 +112,21 @@ export const useStyles = () => { return { marker, + markerWrapper, markersOverlay, range, playHead, }; - }, [euiTheme, euiVars]); + }, [ + euiTheme, + euiVars.euiColorVis0_behindText, + euiVars.euiColorVis1, + euiVars.terminalOutputBackground, + euiVars.terminalOutputMarkerAccent, + euiVars.terminalOutputMarkerWarning, + euiVars.terminalOutputSliderBackground, + progress, + ]); return cached; }; diff --git a/x-pack/plugins/session_view/public/components/tty_text_sizer/index.tsx b/x-pack/plugins/session_view/public/components/tty_text_sizer/index.tsx index 44c4f6801d3ba..42531fc7f5e6c 100644 --- a/x-pack/plugins/session_view/public/components/tty_text_sizer/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_text_sizer/index.tsx @@ -5,7 +5,13 @@ * 2.0. */ import React, { useCallback, useEffect, useState } from 'react'; -import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { + EuiButtonIcon, + EuiButtonIconProps, + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, +} from '@elastic/eui'; import { Teletype } from '../../../common/types/process_tree'; import { DEFAULT_TTY_FONT_SIZE } from '../../../common/constants'; import { ZOOM_IN, ZOOM_FIT, ZOOM_OUT } from './translations'; @@ -19,6 +25,12 @@ export interface TTYTextSizerDeps { onFontSizeChanged(newSize: number): void; } +const commonButtonProps: Partial = { + display: 'empty', + size: 's', + color: 'ghost', +}; + const LINE_HEIGHT_SCALE_RATIO = 1.3; const MINIMUM_FONT_SIZE = 2; const MAXIMUM_FONT_SIZE = 20; @@ -88,6 +100,7 @@ export const TTYTextSizer = ({ display={fit ? 'fill' : 'empty'} iconType={fit ? 'expand' : 'minimize'} onClick={onToggleFit} + {...commonButtonProps} /> @@ -95,12 +108,13 @@ export const TTYTextSizer = ({
- + @@ -108,12 +122,13 @@ export const TTYTextSizer = ({ {`${Math.round((fontSize / DEFAULT_TTY_FONT_SIZE) * 100)}%`} - + diff --git a/x-pack/plugins/session_view/public/components/tty_text_sizer/styles.ts b/x-pack/plugins/session_view/public/components/tty_text_sizer/styles.ts index d233626c1de66..7cc47374f5434 100644 --- a/x-pack/plugins/session_view/public/components/tty_text_sizer/styles.ts +++ b/x-pack/plugins/session_view/public/components/tty_text_sizer/styles.ts @@ -16,14 +16,13 @@ export const useStyles = () => { const ratio: CSSObject = { fontSize: size.m, + color: colors.ghost, }; const separator: CSSObject = { background: colors.lightShade, height: size.xl, width: border.width.thin, - marginLeft: size.xs, - marginRight: size.xs, }; return { diff --git a/x-pack/plugins/session_view/public/components/tty_text_sizer/translations.ts b/x-pack/plugins/session_view/public/components/tty_text_sizer/translations.ts index 0bec7775d43c5..96e5d9d006dea 100644 --- a/x-pack/plugins/session_view/public/components/tty_text_sizer/translations.ts +++ b/x-pack/plugins/session_view/public/components/tty_text_sizer/translations.ts @@ -11,7 +11,7 @@ export const ZOOM_IN = i18n.translate('xpack.sessionView.zoomIn', { }); export const ZOOM_FIT = i18n.translate('xpack.sessionView.zoomFit', { - defaultMessage: 'Zoom fit', + defaultMessage: 'Fit screen', }); export const ZOOM_OUT = i18n.translate('xpack.sessionView.zoomOut', { diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index 0060039f6f2ca..70a8628246b71 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -586,6 +586,41 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; }); }); + describe('#bulkDelete', () => { + test(`throws error if options.namespace is specified`, async () => { + const { client } = createSpacesSavedObjectsClient(); + + await expect( + // @ts-expect-error + client.bulkDelete(null, { namespace: 'bar' }) + ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); + }); + + test(`supplements options with the current namespace`, async () => { + const { client, baseClient } = createSpacesSavedObjectsClient(); + const expectedReturnValue = { statuses: [{ id: 'id', type: 'type', success: true }] }; + baseClient.bulkDelete.mockReturnValue(Promise.resolve(expectedReturnValue)); + + const actualReturnValue = await client.bulkDelete([{ id: 'id', type: 'foo' }], { + force: true, + }); + + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.bulkDelete).toHaveBeenCalledWith( + [ + { + id: 'id', + type: 'foo', + }, + ], + { + namespace: currentSpace.expectedNamespace, + force: true, + } + ); + }); + }); + describe('#removeReferencesTo', () => { test(`throws error if options.namespace is specified`, async () => { const { client } = createSpacesSavedObjectsClient(); diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index a7ef2dae5b386..52ca1f2604e88 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -12,6 +12,8 @@ import type { SavedObject, SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, SavedObjectsBulkGetObject, SavedObjectsBulkResolveObject, SavedObjectsBulkUpdateObject, @@ -139,6 +141,17 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } + async bulkDelete( + objects: SavedObjectsBulkDeleteObject[] = [], + options: SavedObjectsBulkDeleteOptions = {} + ) { + throwErrorIfNamespaceSpecified(options); + return await this.client.bulkDelete(objects, { + ...options, + namespace: spaceIdToNamespace(this.spaceId), + }); + } + async find(options: SavedObjectsFindOptions) { let namespaces: string[]; try { diff --git a/x-pack/plugins/security_solution/public/users/components/constants.ts b/x-pack/plugins/stack_alerts/common/constants.ts similarity index 62% rename from x-pack/plugins/security_solution/public/users/components/constants.ts rename to x-pack/plugins/stack_alerts/common/constants.ts index 059373662cfa8..cac00873face2 100644 --- a/x-pack/plugins/security_solution/public/users/components/constants.ts +++ b/x-pack/plugins/stack_alerts/common/constants.ts @@ -5,5 +5,4 @@ * 2.0. */ -export const RISKY_USERS_DOC_LINK = - 'https://www.github.com/elastic/detection-rules/blob/main/docs/experimental-machine-learning/user-risk-score.md'; +export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; diff --git a/x-pack/plugins/stack_alerts/common/index.ts b/x-pack/plugins/stack_alerts/common/index.ts index 65d05a298224d..1885cbb623b1d 100644 --- a/x-pack/plugins/stack_alerts/common/index.ts +++ b/x-pack/plugins/stack_alerts/common/index.ts @@ -9,4 +9,4 @@ /* eslint-disable @kbn/eslint/no_export_all */ export * from './config'; -export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; +export { STACK_ALERTS_FEATURE_ID } from './constants'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx index d0739baa1ec7a..92db8171450f3 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx @@ -10,14 +10,22 @@ import { EuiCallOut, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { fromKueryExpression, luceneStringToDsl } from '@kbn/es-query'; -import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; -import { DataView } from '@kbn/data-plugin/common'; +import type { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import type { DataView } from '@kbn/data-plugin/common'; import type { Query } from '@kbn/es-query'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; -import { GeoContainmentAlertParams } from '../types'; -import { EntityIndexExpression } from './expressions/entity_index_expression'; -import { EntityByExpression } from './expressions/entity_by_expression'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-server'; +import type { CoreStart } from '@kbn/core/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import { STACK_ALERTS_FEATURE_ID } from '../../../../common/constants'; import { BoundaryIndexExpression } from './expressions/boundary_index_expression'; +import { EntityByExpression } from './expressions/entity_by_expression'; +import { EntityIndexExpression } from './expressions/entity_index_expression'; +import type { GeoContainmentAlertParams } from '../types'; const DEFAULT_VALUES = { TRACKING_EVENT: '', @@ -34,6 +42,15 @@ const DEFAULT_VALUES = { DELAY_OFFSET_WITH_UNITS: '0m', }; +interface KibanaDeps { + http: HttpSetup; + docLinks: DocLinksStart; + uiSettings: IUiSettingsClient; + notifications: CoreStart['notifications']; + storage: IStorageWrapper; + usageCollection: UsageCollectionStart; +} + function validateQuery(query: Query) { try { // eslint-disable-next-line @typescript-eslint/no-unused-expressions @@ -62,6 +79,9 @@ export const GeoContainmentAlertTypeExpression: React.FunctionComponent< boundaryNameField, } = ruleParams; + const { http, docLinks, uiSettings, notifications, storage, usageCollection } = + useKibana().services; + const [indexPattern, _setIndexPattern] = useState({ id: '', title: '', @@ -198,6 +218,17 @@ export const GeoContainmentAlertTypeExpression: React.FunctionComponent< setIndexQueryInput(query); } }} + appName={STACK_ALERTS_FEATURE_ID} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + usageCollection, + }} /> @@ -242,6 +273,17 @@ export const GeoContainmentAlertTypeExpression: React.FunctionComponent< setBoundaryIndexQueryInput(query); } }} + appName={STACK_ALERTS_FEATURE_ID} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + usageCollection, + }} /> diff --git a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts index 70b494a42f121..1d142bbe33c68 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts @@ -46,6 +46,7 @@ export const DEFAULT_COMMON_FIELDS: CommonFields = { [ConfigKey.LOCATIONS]: [], [ConfigKey.NAMESPACE]: DEFAULT_NAMESPACE_STRING, [ConfigKey.MONITOR_SOURCE_TYPE]: SourceType.UI, + [ConfigKey.JOURNEY_ID]: '', }; export const DEFAULT_BROWSER_ADVANCED_FIELDS: BrowserAdvancedFields = { @@ -63,7 +64,6 @@ export const DEFAULT_BROWSER_ADVANCED_FIELDS: BrowserAdvancedFields = { export const DEFAULT_BROWSER_SIMPLE_FIELDS: BrowserSimpleFields = { ...DEFAULT_COMMON_FIELDS, - [ConfigKey.JOURNEY_ID]: '', [ConfigKey.PROJECT_ID]: '', [ConfigKey.PLAYWRIGHT_OPTIONS]: '', [ConfigKey.METADATA]: { @@ -96,6 +96,7 @@ export const DEFAULT_BROWSER_SIMPLE_FIELDS: BrowserSimpleFields = { [ConfigKey.ZIP_URL_TLS_VERSION]: undefined, [ConfigKey.URLS]: '', [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP, + [ConfigKey.TIMEOUT]: null, }; export const DEFAULT_HTTP_SIMPLE_FIELDS: HTTPSimpleFields = { diff --git a/x-pack/plugins/synthetics/common/constants/rest_api.ts b/x-pack/plugins/synthetics/common/constants/rest_api.ts index 85f345c0972fb..d0d783e424f3a 100644 --- a/x-pack/plugins/synthetics/common/constants/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/rest_api.ts @@ -18,7 +18,7 @@ export enum API_URLS { PING_HISTOGRAM = `/internal/uptime/ping/histogram`, SNAPSHOT_COUNT = `/internal/uptime/snapshot/count`, SYNTHETICS_SUCCESSFUL_CHECK = `/internal/uptime/synthetics/check/success`, - JOURNEY_CREATE = `/internal/uptime/journey/{checkGroup}`, + JOURNEY = `/internal/uptime/journey/{checkGroup}`, JOURNEY_FAILED_STEPS = `/internal/uptime/journeys/failed_steps`, JOURNEY_SCREENSHOT = `/internal/uptime/journey/screenshot/{checkGroup}/{stepIndex}`, JOURNEY_SCREENSHOT_BLOCKS = `/internal/uptime/journey/screenshot/block`, diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts index ed8ff0bc50e0d..7b9a1fe380a69 100644 --- a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts @@ -6,6 +6,6 @@ */ export enum SYNTHETICS_API_URLS { - MONITOR_STATUS = `/internal/synthetics/monitor/status`, SYNTHETICS_OVERVIEW = '/internal/synthetics/overview', + PINGS = '/internal/synthetics/pings', } diff --git a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts index 1524e646bb508..f05003f650deb 100644 --- a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts @@ -72,7 +72,6 @@ export const browserFormatters: BrowserFormatMap = { arrayToJsonFormatter(fields[ConfigKey.JOURNEY_FILTERS_TAGS]), [ConfigKey.THROTTLING_CONFIG]: throttlingFormatter, [ConfigKey.IGNORE_HTTPS_ERRORS]: null, - [ConfigKey.JOURNEY_ID]: null, [ConfigKey.PROJECT_ID]: null, [ConfigKey.PLAYWRIGHT_OPTIONS]: null, [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, diff --git a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts index 5a5fbb864ecb3..89bf8793302ba 100644 --- a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts @@ -28,6 +28,7 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.REVISION]: null, [ConfigKey.MONITOR_SOURCE_TYPE]: null, [ConfigKey.FORM_MONITOR_TYPE]: null, + [ConfigKey.JOURNEY_ID]: null, }; export const arrayToJsonFormatter = (value: string[] = []) => diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts index 9d5e28a29a6fc..9f27becbbb023 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts @@ -37,8 +37,8 @@ export const BandwidthLimitKeyCodec = tEnum( export type BandwidthLimitKeyType = t.TypeOf; export const LocationGeoCodec = t.interface({ - lat: t.number, - lon: t.number, + lat: t.union([t.string, t.number]), + lon: t.union([t.string, t.number]), }); export const LocationStatusCodec = tEnum('LocationStatus', LocationStatus); @@ -77,13 +77,13 @@ export const PublicLocationsCodec = t.array(PublicLocationCodec); export const MonitorServiceLocationCodec = t.intersection([ t.interface({ id: t.string, - isServiceManaged: t.boolean, }), t.partial({ label: t.string, geo: LocationGeoCodec, url: t.string, isInvalid: t.boolean, + isServiceManaged: t.boolean, }), ]); diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index ebb5376792d70..58e3a31df8a37 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -8,11 +8,7 @@ import * as t from 'io-ts'; import { secretKeys } from '../../constants/monitor_management'; import { ConfigKey } from './config_key'; -import { - MonitorServiceLocationsCodec, - MonitorServiceLocationCodec, - ServiceLocationErrors, -} from './locations'; +import { MonitorServiceLocationCodec, ServiceLocationErrors } from './locations'; import { DataStream, DataStreamCodec, @@ -25,6 +21,7 @@ import { VerificationModeCodec, } from './monitor_configs'; import { MetadataCodec } from './monitor_meta_data'; +import { PrivateLocationCodec } from './synthetics_private_locations'; const ScheduleCodec = t.interface({ number: t.string, @@ -77,7 +74,7 @@ export const CommonFieldsCodec = t.intersection([ [ConfigKey.SCHEDULE]: ScheduleCodec, [ConfigKey.APM_SERVICE_NAME]: t.string, [ConfigKey.TAGS]: t.array(t.string), - [ConfigKey.LOCATIONS]: MonitorServiceLocationsCodec, + [ConfigKey.LOCATIONS]: t.array(t.union([MonitorServiceLocationCodec, PrivateLocationCodec])), }), t.partial({ [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorTypeCodec, @@ -85,6 +82,7 @@ export const CommonFieldsCodec = t.intersection([ [ConfigKey.REVISION]: t.number, [ConfigKey.MONITOR_SOURCE_TYPE]: SourceTypeCodec, [ConfigKey.CONFIG_ID]: t.string, + [ConfigKey.JOURNEY_ID]: t.string, }), ]); @@ -218,7 +216,6 @@ export const EncryptedBrowserSimpleFieldsCodec = t.intersection([ }), t.partial({ [ConfigKey.PLAYWRIGHT_OPTIONS]: t.string, - [ConfigKey.JOURNEY_ID]: t.string, [ConfigKey.PROJECT_ID]: t.string, [ConfigKey.ORIGINAL_SPACE]: t.string, [ConfigKey.CUSTOM_HEARTBEAT_ID]: t.string, diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts index 072455c8914c7..6abcf4b832135 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts @@ -14,19 +14,21 @@ export const ProjectMonitorThrottlingConfigCodec = t.interface({ latency: t.number, }); -export const ProjectBrowserMonitorCodec = t.intersection([ +export const ProjectMonitorCodec = t.intersection([ t.interface({ + type: t.string, id: t.string, name: t.string, schedule: t.number, - content: t.string, locations: t.array(t.string), }), t.partial({ + content: t.string, + timeout: t.string, privateLocations: t.array(t.string), throttling: ProjectMonitorThrottlingConfigCodec, screenshot: ScreenshotOptionCodec, - tags: t.array(t.string), + tags: t.union([t.string, t.array(t.string)]), ignoreHTTPSErrors: t.boolean, apmServiceName: t.string, playwrightOptions: t.record(t.string, t.unknown), @@ -35,17 +37,21 @@ export const ProjectBrowserMonitorCodec = t.intersection([ }), params: t.record(t.string, t.unknown), enabled: t.boolean, + urls: t.union([t.string, t.array(t.string)]), + hosts: t.union([t.string, t.array(t.string)]), + max_redirects: t.string, + wait: t.string, }), ]); export const ProjectMonitorsRequestCodec = t.interface({ project: t.string, keep_stale: t.boolean, - monitors: t.array(ProjectBrowserMonitorCodec), + monitors: t.array(ProjectMonitorCodec), }); export type ProjectMonitorThrottlingConfig = t.TypeOf; -export type ProjectBrowserMonitor = t.TypeOf; +export type ProjectMonitor = t.TypeOf; export type ProjectMonitorsRequest = t.TypeOf; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts index 69cd76d54db86..556f1e56ed102 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts @@ -7,18 +7,23 @@ import * as t from 'io-ts'; -export const PrivateLocationType = t.intersection([ +export const PrivateLocationCodec = t.intersection([ t.interface({ label: t.string, id: t.string, agentPolicyId: t.string, concurrentMonitors: t.number, }), - t.partial({ geo: t.interface({ lat: t.number, lon: t.number }) }), + t.partial({ + isServiceManaged: t.boolean, + /* Empty Lat lon was accidentally saved as an empty string instead of undefined or null + * Need a migration to fix */ + geo: t.interface({ lat: t.union([t.string, t.number]), lon: t.union([t.string, t.number]) }), + }), ]); export const SyntheticsPrivateLocationsType = t.type({ - locations: t.array(PrivateLocationType), + locations: t.array(PrivateLocationCodec), }); -export type PrivateLocation = t.TypeOf; +export type PrivateLocation = t.TypeOf; export type SyntheticsPrivateLocations = t.TypeOf; diff --git a/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts b/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts index c95f9c281dc92..6f2264d66f9b1 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/ping/synthetics.ts @@ -198,10 +198,23 @@ export const ScreenshotBlockDocType = t.type({ export type ScreenshotBlockDoc = t.TypeOf; +export interface PendingBlock { + status: 'pending' | 'loading'; +} + +export type StoreScreenshotBlock = ScreenshotBlockDoc | PendingBlock; +export interface ScreenshotBlockCache { + [hash: string]: StoreScreenshotBlock; +} + export function isScreenshotBlockDoc(data: unknown): data is ScreenshotBlockDoc { return isRight(ScreenshotBlockDocType.decode(data)); } +export function isPendingBlock(data: unknown): data is PendingBlock { + return ['pending', 'loading'].some((s) => s === (data as PendingBlock)?.status); +} + /** * Contains the fields requried by the Synthetics UI when utilizing screenshot refs. */ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx new file mode 100644 index 0000000000000..81ecb7dc60027 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx @@ -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. + */ + +import { i18n } from '@kbn/i18n'; +import React, { CSSProperties } from 'react'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonIcon, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types'; + +import { useSyntheticsSettingsContext } from '../../../contexts/synthetics_settings_context'; +import { JourneyStep } from '../../../../../../common/runtime_types'; + +import { StatusBadge, parseBadgeStatus, getTextColorForMonitorStatus } from './status_badge'; +import { JourneyStepScreenshotWithLabel } from './journey_step_screenshot_with_label'; +import { StepDurationText } from './step_duration_text'; + +interface Props { + steps: JourneyStep[]; + error?: Error; + loading: boolean; + showStepNumber: boolean; +} + +export function isStepEnd(step: JourneyStep) { + return step.synthetics?.type === 'step/end'; +} + +export const BrowserStepsList = ({ steps, error, loading, showStepNumber = false }: Props) => { + const { euiTheme } = useEuiTheme(); + const stepEnds: JourneyStep[] = steps.filter(isStepEnd); + const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? ''); + + const { basePath } = useSyntheticsSettingsContext(); + + const columns: Array> = [ + ...(showStepNumber + ? [ + { + field: 'synthetics.step.index', + name: '#', + render: (stepIndex: number, item: JourneyStep) => ( + + ), + }, + ] + : []), + { + align: 'left', + field: 'timestamp', + name: STEP_LABEL, + render: (_timestamp: string, item) => ( + + ), + mobileOptions: { + render: (item: JourneyStep) => ( + + + {item.synthetics?.step?.index!}. {item.synthetics?.step?.name} + + + ), + header: STEP_LABEL, + enlarge: true, + }, + }, + { + field: 'synthetics.step.status', + name: RESULT_LABEL, + render: (pingStatus: string) => , + }, + { + align: 'left', + name: STEP_DURATION, + render: (item: JourneyStep) => { + return ; + }, + mobileOptions: { + header: STEP_DURATION, + show: true, + }, + }, + { + align: 'right', + field: 'timestamp', + name: '', + mobileOptions: { show: false }, + render: (_val: string, item) => ( + + ), + }, + ]; + + return ( + <> + + + ); +}; + +const StepNumber = ({ + stepIndex, + step, + euiTheme, +}: { + stepIndex: number; + step: JourneyStep; + euiTheme: EuiThemeComputed; +}) => { + const status = parseBadgeStatus(step.synthetics?.step?.status ?? ''); + + return ( + + {stepIndex} + + ); +}; + +const RESULT_LABEL = i18n.translate('xpack.synthetics.monitor.result.label', { + defaultMessage: 'Result', +}); + +const STEP_LABEL = i18n.translate('xpack.synthetics.monitor.step.label', { + defaultMessage: 'Step', +}); + +const STEP_DURATION = i18n.translate('xpack.synthetics.monitor.step.duration.label', { + defaultMessage: 'Duration', +}); + +const VIEW_DETAILS = i18n.translate('xpack.synthetics.monitor.step.viewDetails', { + defaultMessage: 'View Details', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.test.tsx new file mode 100644 index 0000000000000..d877820ca24fd --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.test.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 React from 'react'; +import { render } from '../../../utils/testing/rtl_helpers'; +import { + EmptyThumbnail, + SCREENSHOT_LOADING_ARIA_LABEL, + SCREENSHOT_NOT_AVAILABLE, +} from './empty_thumbnail'; + +describe('EmptyThumbnail', () => { + it('renders a loading placeholder for loading state', () => { + const { getByLabelText, getByTestId } = render(); + + expect(getByTestId('stepScreenshotPlaceholderLoading')).toBeInTheDocument(); + expect(getByLabelText(SCREENSHOT_LOADING_ARIA_LABEL)); + }); + + it('renders no image available when not loading', () => { + const { queryByTestId, getByLabelText } = render(); + + expect(queryByTestId('stepScreenshotPlaceholderLoading')).not.toBeInTheDocument(); + expect(getByLabelText(SCREENSHOT_NOT_AVAILABLE)); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx new file mode 100644 index 0000000000000..bfc5851ade619 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/empty_thumbnail.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { useEuiTheme, useEuiBackgroundColor, EuiIcon, EuiLoadingContent } from '@elastic/eui'; + +export const THUMBNAIL_WIDTH = 96; +export const THUMBNAIL_HEIGHT = 64; + +export const thumbnailStyle = css` + padding: 0; + margin: 0; + width: ${THUMBNAIL_WIDTH}px; + height: ${THUMBNAIL_HEIGHT}px; + object-fit: contain; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +`; + +export const EmptyThumbnail = ({ + isLoading = false, + width = THUMBNAIL_WIDTH, + height = THUMBNAIL_HEIGHT, +}: { + isLoading: boolean; + width?: number; + height?: number; +}) => { + const { euiTheme } = useEuiTheme(); + + return ( +
+ {isLoading ? ( + + ) : ( + + )} +
+ ); +}; + +export const SCREENSHOT_LOADING_ARIA_LABEL = i18n.translate( + 'xpack.synthetics.monitor.step.screenshot.ariaLabel', + { + defaultMessage: 'Step screenshot is being loaded.', + } +); + +export const SCREENSHOT_NOT_AVAILABLE = i18n.translate( + 'xpack.synthetics.monitor.step.screenshot.notAvailable', + { + defaultMessage: 'Step screenshot is not available.', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.test.tsx new file mode 100644 index 0000000000000..c738f07218842 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.test.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { screen } from '@elastic/eui/lib/test/rtl'; +import { fireEvent, waitFor } from '@testing-library/react'; +import React from 'react'; +import { JourneyStepImagePopover, StepImagePopoverProps } from './journey_step_image_popover'; +import { render } from '../../../utils/testing'; + +describe('JourneyStepImagePopover', () => { + let defaultProps: StepImagePopoverProps; + + beforeEach(() => { + defaultProps = { + captionContent: 'test caption', + imageCaption:
test caption element
, + imgSrc: 'http://sample.com/sampleImageSrc.png', + isImagePopoverOpen: false, + isStepFailed: false, + isLoading: false, + }; + }); + + it('opens displays full-size image on click, hides after close is clicked', async () => { + const { getByAltText } = render(); + + expect(screen.queryByTestSubject('deactivateFullScreenButton')).toBeNull(); + + const caption = getByAltText('test caption'); + fireEvent.click(caption); + + await waitFor(() => { + fireEvent.click(screen.getByTestSubject('deactivateFullScreenButton')); + }); + + await waitFor(() => { + expect(screen.queryByTestSubject('deactivateFullScreenButton')).toBeNull(); + }); + }); + + it('shows the popover when `isOpen` is true', () => { + defaultProps.isImagePopoverOpen = true; + + const { getByAltText } = render(); + + expect(getByAltText(`A larger version of the screenshot for this journey step's thumbnail.`)); + }); + + it('renders caption content', () => { + const { getByRole } = render(); + const image = getByRole('img'); + expect(image).toHaveAttribute('alt', 'test caption'); + expect(image).toHaveAttribute('src', 'http://sample.com/sampleImageSrc.png'); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx new file mode 100644 index 0000000000000..48ff7237223fb --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx @@ -0,0 +1,211 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import { EuiImage, EuiPopover, useEuiTheme } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; +import { useCompositeImage } from '../../../hooks/use_composite_image'; + +import { EmptyThumbnail, thumbnailStyle } from './empty_thumbnail'; + +const POPOVER_IMG_HEIGHT = 360; +const POPOVER_IMG_WIDTH = 640; + +interface ScreenshotImageProps { + captionContent: string; + imageCaption: JSX.Element; + isStepFailed: boolean; + isLoading: boolean; +} + +const ScreenshotThumbnail: React.FC = ({ + captionContent, + imageCaption, + imageData, + isStepFailed, + isLoading, +}) => { + return imageData ? ( + + ) : ( + + ); +}; +/** + * This component provides an intermediate step for composite images. It causes a loading spinner to appear + * while the image is being re-assembled, then calls the default image component and provides a data URL for the image. + */ +const RecomposedScreenshotImage: React.FC< + ScreenshotImageProps & { + imgRef: ScreenshotRefImageData; + setImageData: React.Dispatch; + imageData: string | undefined; + } +> = ({ + captionContent, + imageCaption, + imageData, + imgRef, + setImageData, + isStepFailed, + isLoading, +}) => { + // initially an undefined URL value is passed to the image display, and a loading spinner is rendered. + // `useCompositeImage` will call `setImageData` when the image is composited, and the updated `imageData` will display. + useCompositeImage(imgRef, setImageData, imageData); + + return ( + + ); +}; + +export interface StepImagePopoverProps { + captionContent: string; + imageCaption: JSX.Element; + imgSrc?: string; + imgRef?: ScreenshotRefImageData; + isImagePopoverOpen: boolean; + isStepFailed: boolean; + isLoading: boolean; +} + +const JourneyStepImage: React.FC< + Omit & { + setImageData: React.Dispatch; + imageData: string | undefined; + } +> = ({ + captionContent, + imageCaption, + imageData, + imgRef, + imgSrc, + setImageData, + isStepFailed, + isLoading, +}) => { + if (imgSrc) { + return ( + + ); + } else if (imgRef) { + return ( + + ); + } + return null; +}; + +export const JourneyStepImagePopover: React.FC = ({ + captionContent, + imageCaption, + imgRef, + imgSrc, + isImagePopoverOpen, + isStepFailed, + isLoading, +}) => { + const { euiTheme } = useEuiTheme(); + + const [imageData, setImageData] = React.useState(imgSrc || undefined); + + React.useEffect(() => { + // for legacy screenshots, when a new image arrives, we must overwrite it + if (imgSrc && imgSrc !== imageData) { + setImageData(imgSrc); + } + }, [imgSrc, imageData]); + + const setImageDataCallback = React.useCallback( + (newImageData: string | undefined) => setImageData(newImageData), + [setImageData] + ); + + const isImageLoading = isLoading || (!!imgRef && !imageData); + + return ( + + } + isOpen={isImagePopoverOpen} + closePopover={() => {}} + > + {imageData && !isLoading ? ( + + ) : ( + + )} + + ); +}; + +export const fullSizeImageAlt = i18n.translate('xpack.synthetics.monitor.step.thumbnail.alt', { + defaultMessage: `A larger version of the screenshot for this journey step's thumbnail.`, +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx new file mode 100644 index 0000000000000..4c95fade23d1a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.test.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. 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, waitFor } from '@testing-library/react'; +import moment from 'moment'; +import { JourneyStepScreenshotContainer } from './journey_step_screenshot_container'; +import { render } from '../../../utils/testing'; +import * as observabilityPublic from '@kbn/observability-plugin/public'; +import { getShortTimeStamp } from '../../../utils/monitor_test_result/timestamp'; +import '../../../utils/testing/__mocks__/use_composite_image.mock'; +import { mockRef } from '../../../utils/testing/__mocks__/screenshot_ref.mock'; +import * as retrieveHooks from './use_retrieve_step_image'; + +jest.mock('@kbn/observability-plugin/public'); + +describe('JourneyStepScreenshotContainer', () => { + let checkGroup: string; + let timestamp: string; + const { FETCH_STATUS } = observabilityPublic; + + beforeAll(() => { + checkGroup = 'f58a484f-2ffb-11eb-9b35-025000000001'; + timestamp = '2020-11-26T15:28:56.896Z'; + }); + + it.each([[FETCH_STATUS.PENDING], [FETCH_STATUS.LOADING]])( + 'displays spinner when loading step image', + (fetchStatus) => { + jest + .spyOn(observabilityPublic, 'useFetcher') + .mockReturnValue({ status: fetchStatus, data: null, refetch: () => null, loading: true }); + const { getByTestId } = render( + + ); + expect(getByTestId('stepScreenshotPlaceholderLoading')).toBeInTheDocument(); + } + ); + + it('displays no image available when img src is unavailable and fetch status is successful', () => { + jest + .spyOn(observabilityPublic, 'useFetcher') + .mockReturnValue({ status: FETCH_STATUS.SUCCESS, data: null, refetch: () => null }); + const { getByTestId } = render( + + ); + expect(getByTestId('stepScreenshotNotAvailable')).toBeInTheDocument(); + }); + + it('displays image when img src is available from useFetcher', () => { + const src = 'http://sample.com/sampleImageSrc.png'; + jest.spyOn(retrieveHooks, 'useRetrieveStepImage').mockReturnValue({ + loading: false, + data: { maxSteps: 2, stepName: 'test', src }, + attempts: 1, + }); + + const { container } = render( + + ); + expect(container.querySelector('img')?.src).toBe(src); + }); + + it('displays popover image when mouse enters img caption, and hides onLeave', async () => { + const src = 'http://sample.com/sampleImageSrc.png'; + jest.spyOn(retrieveHooks, 'useRetrieveStepImage').mockReturnValue({ + loading: false, + data: { maxSteps: 1, stepName: null, src }, + attempts: 1, + }); + const { getByAltText, getAllByText, queryByAltText } = render( + + ); + + const caption = getAllByText('Nov 26, 2020 10:28:56 AM'); + fireEvent.mouseEnter(caption[0]); + + const altText = `A larger version of the screenshot for this journey step's thumbnail.`; + + await waitFor(() => getByAltText(altText)); + + fireEvent.mouseLeave(caption[0]); + + await waitFor(() => expect(queryByAltText(altText)).toBeNull()); + }); + + it('handles screenshot ref data', async () => { + jest.spyOn(retrieveHooks, 'useRetrieveStepImage').mockReturnValue({ + loading: false, + data: mockRef, + attempts: 1, + }); + + const { getByAltText, getByText, getByRole, getAllByText, queryByAltText } = render( + + ); + + await waitFor(() => getByRole('img')); + const caption = getAllByText('Nov 26, 2020 10:28:56 AM'); + fireEvent.mouseEnter(caption[0]); + + const altText = `A larger version of the screenshot for this journey step's thumbnail.`; + + await waitFor(() => getByAltText(altText)); + + fireEvent.mouseLeave(caption[0]); + + await waitFor(() => expect(queryByAltText(altText)).toBeNull()); + expect(getByText('Step: 1 of 1')); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.tsx new file mode 100644 index 0000000000000..6c2653f7efaa6 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_container.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 React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import useIntersection from 'react-use/lib/useIntersection'; +import { i18n } from '@kbn/i18n'; + +import { + isScreenshotImageBlob, + isScreenshotRef, + ScreenshotRefImageData, +} from '../../../../../../common/runtime_types'; + +import { SyntheticsSettingsContext } from '../../../contexts'; + +import { useRetrieveStepImage } from './use_retrieve_step_image'; +import { ScreenshotOverlayFooter } from './screenshot_overlay_footer'; +import { JourneyStepImagePopover } from './journey_step_image_popover'; +import { EmptyThumbnail } from './empty_thumbnail'; + +interface Props { + checkGroup?: string; + stepLabels?: string[]; + stepStatus?: string; + initialStepNo?: number; + allStepsLoaded?: boolean; + retryFetchOnRevisit?: boolean; // Set to `true` fro "Run Once" / "Test Now" modes +} + +export const JourneyStepScreenshotContainer = ({ + stepLabels = [], + checkGroup, + stepStatus, + allStepsLoaded, + initialStepNo = 1, + retryFetchOnRevisit = false, +}: Props) => { + const [stepNumber, setStepNumber] = useState(initialStepNo); + const [isImagePopoverOpen, setIsImagePopoverOpen] = useState(false); + + const [stepImages, setStepImages] = useState([]); + + const intersectionRef = React.useRef(null); + + const { basePath } = useContext(SyntheticsSettingsContext); + + const imgPath = `${basePath}/internal/uptime/journey/screenshot/${checkGroup}/${stepNumber}`; + const stepLabel = stepLabels[stepNumber - 1] ?? ''; + + const intersection = useIntersection(intersectionRef, { + root: null, + rootMargin: '0px', + threshold: 1, + }); + + const [screenshotRef, setScreenshotRef] = useState(undefined); + + const isScreenshotRefValid = Boolean( + screenshotRef && screenshotRef?.ref?.screenshotRef?.synthetics?.step?.index === stepNumber + ); + const { data, loading } = useRetrieveStepImage({ + hasImage: Boolean(stepImages[stepNumber - 1]) || isScreenshotRefValid, + hasIntersected: Boolean(intersection && intersection.intersectionRatio === 1), + stepStatus, + imgPath, + retryFetchOnRevisit, + }); + + useEffect(() => { + if (isScreenshotRef(data)) { + setScreenshotRef(data); + } else if (isScreenshotImageBlob(data)) { + setStepImages((prevState) => [...prevState, data?.src]); + } + }, [data]); + + let imgSrc; + if (isScreenshotImageBlob(data)) { + imgSrc = stepImages?.[stepNumber - 1] ?? data.src; + } + + const captionContent = formatCaptionContent(stepNumber, data?.maxSteps); + + const [numberOfCaptions, setNumberOfCaptions] = useState(0); + + // Overlay Footer has next and previous buttons to traverse journey's steps + const overlayFooter = ( + setNumberOfCaptions((prevVal) => (val ? prevVal + 1 : prevVal - 1))} + /> + ); + + useEffect(() => { + // This is a hack to get state if image is in full screen, we should refactor + // it once eui image exposes it's full screen state + // we are checking if number of captions are 2, that means + // image is in full screen mode since caption is also rendered on + // full screen image + // we dont want to change image displayed in thumbnail + if (numberOfCaptions === 1 && stepNumber !== initialStepNo) { + setStepNumber(initialStepNo); + } + }, [numberOfCaptions, initialStepNo, stepNumber]); + + return ( +
setIsImagePopoverOpen(true)} + onMouseLeave={() => setIsImagePopoverOpen(false)} + ref={intersectionRef} + > + {imgSrc || screenshotRef ? ( + + ) : ( + + )} +
+ ); +}; + +export const formatCaptionContent = (stepNumber: number, totalSteps?: number) => + i18n.translate('xpack.synthetics.monitor.stepOfSteps', { + defaultMessage: 'Step: {stepNumber} of {totalSteps}', + values: { + stepNumber, + totalSteps, + }, + }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.tsx new file mode 100644 index 0000000000000..12dcd4db95f00 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_screenshot_with_label.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 React, { CSSProperties } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui'; +import { JourneyStep } from '../../../../../../common/runtime_types'; +import { JourneyStepScreenshotContainer } from './journey_step_screenshot_container'; +import { getTextColorForMonitorStatus, parseBadgeStatus } from './status_badge'; + +interface Props { + step: JourneyStep; + stepLabels?: string[]; + allStepsLoaded?: boolean; + compactView?: boolean; +} + +export const JourneyStepScreenshotWithLabel = ({ + step, + stepLabels = [], + compactView, + allStepsLoaded, +}: Props) => { + const { euiTheme } = useEuiTheme(); + const status = parseBadgeStatus(step.synthetics.step?.status ?? ''); + const textColor = euiTheme.colors[getTextColorForMonitorStatus(status)] as CSSProperties['color']; + + return ( + + + + + + + {step.synthetics?.step?.name} + + + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.test.tsx new file mode 100644 index 0000000000000..f38084971114b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.test.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fireEvent, waitFor } from '@testing-library/react'; +import React from 'react'; +import { render } from '../../../utils/testing'; +import { ScreenshotOverlayFooter, ScreenshotOverlayFooterProps } from './screenshot_overlay_footer'; +import { getShortTimeStamp } from '../../../utils/monitor_test_result/timestamp'; +import moment from 'moment'; +import { mockRef } from '../../../utils/testing/__mocks__/screenshot_ref.mock'; + +describe('ScreenshotOverlayFooter', () => { + let defaultProps: ScreenshotOverlayFooterProps; + + beforeEach(() => { + defaultProps = { + captionContent: 'test caption content', + imgSrc: 'http://sample.com/sampleImageSrc.png', + maxSteps: 3, + setStepNumber: jest.fn(), + stepNumber: 2, + label: getShortTimeStamp(moment('2020-11-26T15:28:56.896Z')), + onVisible: jest.fn(), + isLoading: false, + }; + }); + + it('labels prev and next buttons', () => { + const { getByLabelText } = render(); + + expect(getByLabelText('Previous step')); + expect(getByLabelText('Next step')); + }); + + it('increments step number on next click', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Next step'); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); + expect(defaultProps.setStepNumber).toHaveBeenCalledWith(3); + }); + }); + + it('decrements step number on prev click', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Previous step'); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); + expect(defaultProps.setStepNumber).toHaveBeenCalledWith(1); + }); + }); + + it('disables `next` button on final step', () => { + defaultProps.stepNumber = 3; + + const { getByLabelText } = render(); + + // getByLabelText('Next step'); + expect(getByLabelText('Next step')).toHaveAttribute('disabled'); + expect(getByLabelText('Previous step')).not.toHaveAttribute('disabled'); + }); + + it('disables `prev` button on final step', () => { + defaultProps.stepNumber = 1; + + const { getByLabelText } = render(); + + expect(getByLabelText('Next step')).not.toHaveAttribute('disabled'); + expect(getByLabelText('Previous step')).toHaveAttribute('disabled'); + }); + + it('renders a timestamp', () => { + const { getByText } = render(); + + getByText('Nov 26, 2020 10:28:56 AM'); + }); + + it('renders caption content', () => { + const { getByText } = render(); + + getByText('test caption content'); + }); + + it('renders caption content for screenshot ref data', async () => { + const { getByText } = render( + + ); + + getByText('test caption content'); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.tsx new file mode 100644 index 0000000000000..3a01a74721d40 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/screenshot_overlay_footer.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { MouseEvent, useEffect } from 'react'; +import { css } from '@emotion/react'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiText, + useEuiTheme, + useIsWithinMaxBreakpoint, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; + +export interface ScreenshotOverlayFooterProps { + captionContent: string; + imgSrc?: string; + imgRef?: ScreenshotRefImageData; + maxSteps?: number; + setStepNumber: React.Dispatch>; + stepNumber: number; + label?: string; + onVisible: (val: boolean) => void; + isLoading: boolean; +} + +export const ScreenshotOverlayFooter: React.FC = ({ + captionContent, + imgRef, + imgSrc, + maxSteps, + setStepNumber, + stepNumber, + isLoading, + label, + onVisible, +}) => { + const { euiTheme } = useEuiTheme(); + + useEffect(() => { + onVisible(true); + return () => { + onVisible(false); + }; + // Empty deps to only trigger effect once on init + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const isSmall = useIsWithinMaxBreakpoint('m'); + + return ( +
{ + // we don't want this to be captured by row click which leads to step list page + evt.stopPropagation(); + }} + onKeyDown={(evt) => { + // Just to satisfy ESLint + }} + > +
+ {(imgSrc || imgRef) && ( + + + ) => { + setStepNumber(stepNumber - 1); + evt.preventDefault(); + }} + iconType="arrowLeft" + aria-label={prevAriaLabel} + isLoading={isLoading} + > + {prevAriaLabel} + + + + {captionContent} + + + ) => { + setStepNumber(stepNumber + 1); + evt.stopPropagation(); + }} + iconType="arrowRight" + iconSide="right" + aria-label={nextAriaLabel} + isLoading={isLoading} + > + {nextAriaLabel} + + + + )} + + {label} + +
+
+ ); +}; + +export const prevAriaLabel = i18n.translate('xpack.synthetics.monitor.step.previousStep', { + defaultMessage: 'Previous step', +}); + +export const nextAriaLabel = i18n.translate('xpack.synthetics.monitor.step.nextStep', { + defaultMessage: 'Next step', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/single_ping_result.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/single_ping_result.tsx new file mode 100644 index 0000000000000..c1da7e0035097 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/single_ping_result.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiBadge, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Ping } from '../../../../../../common/runtime_types'; +import { formatTestDuration } from '../../../utils/monitor_test_result/test_time_formats'; + +export const SinglePingResult = ({ ping, loading }: { ping: Ping; loading: boolean }) => { + const ip = !loading ? ping?.resolve?.ip : undefined; + const durationUs = !loading ? ping?.monitor?.duration?.us : undefined; + const rtt = !loading ? ping?.resolve?.rtt?.us : undefined; + const url = !loading ? ping?.url?.full : undefined; + const responseStatus = !loading ? ping?.http?.response?.status_code : undefined; + + return ( + + IP + {ip} + {DURATION_LABEL} + + {formatTestDuration(durationUs)} + + rtt + {formatTestDuration(rtt)} + URL + {url} + + {responseStatus ? ( + <> + Response status + + {responseStatus} + + + ) : null} + + ); +}; + +const DURATION_LABEL = i18n.translate('xpack.synthetics.monitor.duration.label', { + defaultMessage: 'Duration', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx new file mode 100644 index 0000000000000..a18c0d7a2ca7e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiBadge, IconColor, EuiThemeComputed } from '@elastic/eui'; + +type MonitorStatus = 'succeeded' | 'failed' | 'skipped'; +export const StatusBadge = ({ status }: { status: MonitorStatus }) => { + return ( + + {status === 'succeeded' ? COMPLETE_LABEL : status === 'failed' ? FAILED_LABEL : SKIPPED_LABEL} + + ); +}; + +export const parseBadgeStatus = (status: string) => { + switch (status) { + case 'succeeded': + case 'success': + case 'up': + return 'succeeded'; + case 'fail': + case 'failed': + case 'down': + return 'failed'; + case 'skip': + case 'skipped': + return 'skipped'; + default: + return 'skipped'; + } +}; + +export const getBadgeColorForMonitorStatus = (status: MonitorStatus): IconColor => { + return status === 'succeeded' ? 'success' : status === 'failed' ? 'danger' : 'default'; +}; + +export const getTextColorForMonitorStatus = ( + status: MonitorStatus +): keyof EuiThemeComputed['colors'] => { + return status === 'skipped' ? 'disabledText' : 'text'; +}; + +export const COMPLETE_LABEL = i18n.translate('xpack.synthetics.monitorStatus.complete', { + defaultMessage: 'Complete', +}); + +export const FAILED_LABEL = i18n.translate('xpack.synthetics.monitorStatus.failed', { + defaultMessage: 'Failed', +}); + +export const SKIPPED_LABEL = i18n.translate('xpack.synthetics.monitorStatus.skipped', { + defaultMessage: 'Skipped', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/step_duration_text.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/step_duration_text.tsx new file mode 100644 index 0000000000000..68f5d919f90e3 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/step_duration_text.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, { CSSProperties, useMemo } from 'react'; +import { EuiText, useEuiTheme } from '@elastic/eui'; +import { JourneyStep } from '../../../../../../common/runtime_types'; +import { formatTestDuration } from '../../../utils/monitor_test_result/test_time_formats'; + +import { parseBadgeStatus, getTextColorForMonitorStatus } from './status_badge'; + +export const StepDurationText = ({ step }: { step: JourneyStep }) => { + const { euiTheme } = useEuiTheme(); + + const stepDuration = useMemo(() => { + const status = parseBadgeStatus(step.synthetics.step?.status ?? ''); + const color = euiTheme.colors[getTextColorForMonitorStatus(status)] as CSSProperties['color']; + if (status === 'skipped') { + return { text: '-', color }; + } + + return { + text: formatTestDuration(step.synthetics.step?.duration?.us), + color, + }; + }, [euiTheme.colors, step.synthetics.step?.duration?.us, step.synthetics.step?.status]); + + return ( + + {stepDuration.text} + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.ts new file mode 100644 index 0000000000000..f840658a3dc48 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/use_retrieve_step_image.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 { useState } from 'react'; +import { useFetcher } from '@kbn/observability-plugin/public'; +import { + ScreenshotImageBlob, + ScreenshotRefImageData, +} from '../../../../../../common/runtime_types'; +import { getJourneyScreenshot } from '../../../state'; + +type ImageResponse = ScreenshotImageBlob | ScreenshotRefImageData | null; +interface DataResult { + [imgPath: string]: { attempts: number; data: ImageResponse; loading: boolean }; +} + +function getUpdatedState( + prevState: DataResult, + imgPath: string, + increment: boolean, + data?: ImageResponse, + loading?: boolean +) { + const newAttempts = (prevState[imgPath]?.attempts ?? 0) + (increment ? 1 : 0); + const newData = data ?? prevState[imgPath]?.data ?? null; + const newLoading = loading ?? prevState[imgPath]?.loading ?? false; + return { + ...prevState, + [imgPath]: { attempts: newAttempts, data: newData, loading: newLoading }, + }; +} + +export const useRetrieveStepImage = ({ + stepStatus, + hasImage, + hasIntersected, + imgPath, + retryFetchOnRevisit, +}: { + imgPath: string; + stepStatus?: string; + hasImage: boolean; + hasIntersected: boolean; + + /** + * Whether to retry screenshot image fetch on revisit (when intersection change triggers). + * Will only re-fetch if an image fetch wasn't successful in previous attempts. + * Set this to `true` fro "Run Once" / "Test Now" modes + */ + retryFetchOnRevisit: boolean; +}) => { + const [imgState, setImgState] = useState({}); + + const skippedStep = stepStatus === 'skipped'; + + useFetcher(() => { + const hasBeenRetriedBefore = (imgState[imgPath]?.attempts ?? 0) > 0; + const shouldRetry = retryFetchOnRevisit || !hasBeenRetriedBefore; + + if (!skippedStep && hasIntersected && !hasImage && shouldRetry) { + setImgState((prev) => { + return getUpdatedState(prev, imgPath, false, undefined, true); + }); + return getJourneyScreenshot(imgPath) + .then((resp) => { + setImgState((prev) => { + return getUpdatedState(prev, imgPath, true, resp, false); + }); + + return resp; + }) + .catch(() => { + setImgState((prev) => { + return getUpdatedState(prev, imgPath, true, null, false); + }); + }); + } else { + return new Promise((resolve) => resolve(null)); + } + }, [skippedStep, hasIntersected, imgPath, retryFetchOnRevisit]); + + return imgState[imgPath] ?? { data: null, loading: false }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.test.tsx index bd58aa12b1c82..e7b166fe7b5fa 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.test.tsx @@ -11,14 +11,6 @@ import 'jest-styled-components'; import { render } from '../../../utils/testing/rtl_helpers'; import { SyntheticsPageTemplateComponent } from './synthetics_page_template'; import { OVERVIEW_ROUTE } from '../../../../../../common/constants'; -import { useBreakpoints } from '../../../../../hooks/use_breakpoints'; - -jest.mock('../../../../../hooks/use_breakpoints', () => { - const down = jest.fn().mockReturnValue(false); - return { - useBreakpoints: () => ({ down }), - }; -}); describe('SyntheticsPageTemplateComponent', () => { describe('styling', () => { @@ -34,7 +26,7 @@ describe('SyntheticsPageTemplateComponent', () => { }); it('applies the header centering on mobile', () => { - (useBreakpoints().down as jest.Mock).mockReturnValue(true); + window.innerWidth = 600; const { container } = render(); expect(container.firstChild).toBeDefined(); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.tsx index 657ad3cb2391c..dcd139727a138 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.tsx @@ -7,14 +7,13 @@ import React, { useEffect, useMemo } from 'react'; import styled from 'styled-components'; -import { EuiPageHeaderProps, EuiPageTemplateProps } from '@elastic/eui'; +import { EuiPageHeaderProps, EuiPageTemplateProps, useIsWithinMaxBreakpoint } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; import { ClientPluginsStart } from '../../../../../plugin'; import { EmptyStateLoading } from '../../monitors_page/overview/empty_state/empty_state_loading'; import { EmptyStateError } from '../../monitors_page/overview/empty_state/empty_state_error'; import { useHasData } from '../../monitors_page/overview/empty_state/use_has_data'; -import { useBreakpoints } from '../../../hooks'; interface Props { path: string; @@ -36,8 +35,7 @@ export const SyntheticsPageTemplateComponent: React.FC(); - const { down } = useBreakpoints(); - const isMobile = down('s'); + const isMobile = useIsWithinMaxBreakpoint('s'); const PageTemplateComponent = observability.navigation.PageTemplate; const StyledPageTemplateComponent = useMemo(() => { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx index 6d7fa2cbc441f..e5d8c8152d68f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx @@ -58,3 +58,7 @@ const MonacoCodeContainer = euiStyled.div` z-index: 0; } `; + +export const JSONEditor = (props: any) => { + return ; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx index 83ba909f3063c..644901aadd5c2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx @@ -29,6 +29,7 @@ import { EuiLink, EuiTextArea, } from '@elastic/eui'; +import { getDocLinks } from '../../../../../kibana_services'; import { useMonitorName } from '../hooks/use_monitor_name'; import { MonitorTypeRadioGroup } from '../fields/monitor_type_radio_group'; import { @@ -53,6 +54,7 @@ import { ComboBox } from '../fields/combo_box'; import { SourceField } from '../fields/source_field'; import { getDefaultFormFields } from './defaults'; import { validate, validateHeaders, WHOLE_NUMBERS_ONLY, FLOATS_ONLY } from './validation'; +import { JSONEditor } from '../fields/code_editor'; const getScheduleContent = (value: number) => { if (value > 60) { @@ -1006,4 +1008,56 @@ export const FIELD: Record = { required: true, }), }, + [ConfigKey.PLAYWRIGHT_OPTIONS]: { + fieldKey: ConfigKey.PLAYWRIGHT_OPTIONS, + component: JSONEditor, + label: i18n.translate('xpack.synthetics.monitorConfig.playwrightOptions.label', { + defaultMessage: 'Playwright options', + }), + helpText: ( + + {i18n.translate('xpack.synthetics.monitorConfig.playwrightOptions.helpText', { + defaultMessage: 'Configure Playwright agent with custom options. ', + })} + + {i18n.translate('xpack.synthetics.monitorConfig.playwrightOptions.learnMore', { + defaultMessage: 'Learn more', + })} + + + ), + error: i18n.translate('xpack.synthetics.monitorConfig.playwrightOptions.error', { + defaultMessage: 'Invalid JSON format', + }), + ariaLabel: i18n.translate( + 'xpack.synthetics.monitorConfig.playwrightOptions.codeEditor.json.ariaLabel', + { + defaultMessage: 'Playwright options JSON code editor', + } + ), + controlled: true, + required: false, + props: ({ + field, + setValue, + }: { + field?: ControllerRenderProps; + setValue: UseFormReturn['setValue']; + }) => ({ + onChange: (json: string) => setValue(ConfigKey.PLAYWRIGHT_OPTIONS, json), + }), + validation: () => ({ + validate: (value) => { + const validateFn = validate[DataStream.BROWSER][ConfigKey.PLAYWRIGHT_OPTIONS]; + if (validateFn) { + return !validateFn({ + [ConfigKey.PLAYWRIGHT_OPTIONS]: value, + }); + } + }, + }), + }, }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx index 9f11f2c53c06e..132e3ab0343e1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx @@ -103,6 +103,21 @@ export const TCP_ADVANCED = { }, }; +export const BROWSER_ADVANCED = [ + { + title: i18n.translate('xpack.synthetics.monitorConfig.section.syntAgentOptions.title', { + defaultMessage: 'Synthetics agent options', + }), + description: i18n.translate( + 'xpack.synthetics.monitorConfig.section.syntAgentOptions.description', + { + defaultMessage: 'Provide fine-tuned configuration for the synthetics agent.', + } + ), + components: [FIELD[`${ConfigKey.PLAYWRIGHT_OPTIONS}`]], + }, +]; + interface AdvancedFieldGroup { title: string; description: string; @@ -197,6 +212,7 @@ export const FORM_CONFIG: FieldConfig = { FIELD[ConfigKey.NAMESPACE], ], }, + ...BROWSER_ADVANCED, ], }, [FormMonitorType.SINGLE]: { @@ -220,6 +236,7 @@ export const FORM_CONFIG: FieldConfig = { FIELD[ConfigKey.NAMESPACE], ], }, + ...BROWSER_ADVANCED, ], }, [FormMonitorType.ICMP]: { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx index 1cf2baf1df94f..4b4e778b87b9b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx @@ -104,6 +104,7 @@ describe('format', () => { 'check.response.status': [], enabled, form_monitor_type: 'http', + journey_id: '', locations: [ { id: 'us_central', @@ -316,6 +317,7 @@ describe('format', () => { 'check.response.status': [], enabled: true, form_monitor_type: 'http', + journey_id: '', locations: [ { id: 'us_central', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx index de3ce4bc327fd..00330134344c4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx @@ -57,6 +57,21 @@ export const validateTimeout = ({ return parseFloat(timeout) > schedule; }; +export const validJSONFormat = (value: string) => { + let obj; + + try { + obj = JSON.parse(value); + if (!obj || typeof obj !== 'object') { + return false; + } + } catch (e) { + return false; + } + + return true; +}; + // validation functions return true when invalid const validateCommon: ValidationLibrary = { [ConfigKey.SCHEDULE]: ({ [ConfigKey.SCHEDULE]: value }) => { @@ -145,6 +160,8 @@ const validateBrowser: ValidationLibrary = { [ConfigKey.UPLOAD_SPEED]: ({ [ConfigKey.UPLOAD_SPEED]: uploadSpeed }) => validateThrottleValue(uploadSpeed), [ConfigKey.LATENCY]: ({ [ConfigKey.LATENCY]: latency }) => validateThrottleValue(latency, true), + [ConfigKey.PLAYWRIGHT_OPTIONS]: ({ [ConfigKey.PLAYWRIGHT_OPTIONS]: playwrightOptions }) => + playwrightOptions ? !validJSONFormat(playwrightOptions) : false, }; export type ValidateDictionary = Record; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx new file mode 100644 index 0000000000000..a6d2070d0e96a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.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 { useFetcher } from '@kbn/observability-plugin/public'; +import { SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types'; +import { fetchJourneySteps } from '../../../state'; + +export const useJourneySteps = (checkGroup: string | undefined) => { + const { data, loading } = useFetcher(() => { + if (!checkGroup) { + return Promise.resolve(null); + } + + return fetchJourneySteps({ checkGroup }); + }, [checkGroup]); + + return { data: data as SyntheticsJourneyApiResponse, loading: loading ?? false }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/hooks/use_selected_location.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx similarity index 79% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/hooks/use_selected_location.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx index 895168657ba1b..779b8d8001ad5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/hooks/use_selected_location.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx @@ -7,13 +7,13 @@ import { useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { selectSelectedLocationId, setMonitorSummaryLocationAction } from '../../../state'; +import { selectSelectedLocationId, setMonitorDetailsLocationAction } from '../../../state'; import { useUrlParams, useLocations } from '../../../hooks'; export const useSelectedLocation = () => { const [getUrlParams, updateUrlParams] = useUrlParams(); const { locations } = useLocations(); - const selectedLocation = useSelector(selectSelectedLocationId); + const selectedLocationId = useSelector(selectSelectedLocationId); const dispatch = useDispatch(); const { locationId: urlLocationId } = getUrlParams(); @@ -26,10 +26,10 @@ export const useSelectedLocation = () => { } } - if (urlLocationId && selectedLocation !== urlLocationId) { - dispatch(setMonitorSummaryLocationAction(urlLocationId)); + if (urlLocationId && selectedLocationId !== urlLocationId) { + dispatch(setMonitorDetailsLocationAction(urlLocationId)); } - }, [dispatch, updateUrlParams, locations, urlLocationId, selectedLocation]); + }, [dispatch, updateUrlParams, locations, urlLocationId, selectedLocationId]); return useMemo( () => locations.find((loc) => loc.id === urlLocationId) ?? null, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx new file mode 100644 index 0000000000000..6e4d972fb445b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.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 { useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { + getMonitorAction, + selectEncryptedSyntheticsSavedMonitors, + selectMonitorListState, + selectorMonitorDetailsState, +} from '../../../state'; + +export const useSelectedMonitor = () => { + const { monitorId } = useParams<{ monitorId: string }>(); + const monitorsList = useSelector(selectEncryptedSyntheticsSavedMonitors); + const { loading: monitorListLoading } = useSelector(selectMonitorListState); + const monitorFromList = useMemo( + () => monitorsList.find((monitor) => monitor.id === monitorId) ?? null, + [monitorId, monitorsList] + ); + + const { syntheticsMonitor, syntheticsMonitorLoading } = useSelector(selectorMonitorDetailsState); + const dispatch = useDispatch(); + + const isMonitorFromListValid = monitorId && monitorFromList && monitorFromList?.id === monitorId; + const isLoadedSyntheticsMonitorValid = + monitorId && syntheticsMonitor && syntheticsMonitor?.id === monitorId; + const availableMonitor = isLoadedSyntheticsMonitorValid + ? syntheticsMonitor + : isMonitorFromListValid + ? monitorFromList + : null; + + useEffect(() => { + if (monitorId && !availableMonitor && !syntheticsMonitorLoading) { + dispatch(getMonitorAction.get({ monitorId })); + } + }, [dispatch, monitorId, availableMonitor, syntheticsMonitorLoading]); + + return { + monitor: availableMonitor, + loading: syntheticsMonitorLoading || monitorListLoading, + }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/last_run_info.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/last_run_info.tsx similarity index 98% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/last_run_info.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/last_run_info.tsx index 4c3f4e885bc86..d69cde15f734d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/last_run_info.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/last_run_info.tsx @@ -16,7 +16,6 @@ import { useSelectedLocation } from './hooks/use_selected_location'; export const MonitorSummaryLastRunInfo = ({ ping }: { ping: Ping }) => { const selectedLocation = useSelectedLocation(); const isBrowserType = ping.monitor.type === 'browser'; - const theme = useTheme(); return ( @@ -26,7 +25,7 @@ export const MonitorSummaryLastRunInfo = ({ ping }: { ping: Ping }) => { {isBrowserType ? SUCCESS_LABEL : UP_LABEL} - ) : ping.monitor.status === 'up' ? ( + ) : ping.monitor.status === 'down' ? ( {isBrowserType ? FAILED_LABEL : DOWN_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx new file mode 100644 index 0000000000000..ee9ba46c561bb --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.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, { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { useSelectedMonitor } from './hooks/use_selected_monitor'; +import { useSelectedLocation } from './hooks/use_selected_location'; +import { getMonitorAction, getMonitorRecentPingsAction } from '../../state/monitor_details'; +import { useMonitorListBreadcrumbs } from '../monitors_page/hooks/use_breadcrumbs'; +export const MonitorDetailsPage = () => { + const { monitor } = useSelectedMonitor(); + + useMonitorListBreadcrumbs([{ text: monitor?.name ?? '' }]); + + const dispatch = useDispatch(); + + const selectedLocation = useSelectedLocation(); + const { monitorId } = useParams<{ monitorId: string }>(); + + useEffect(() => { + dispatch(getMonitorAction.get({ monitorId })); + + if (selectedLocation) { + dispatch(getMonitorRecentPingsAction.get({ monitorId, locationId: selectedLocation.id })); + } + }, [dispatch, monitorId, selectedLocation]); + + return <>; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_header.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_header.tsx new file mode 100644 index 0000000000000..0425ab3ce309e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_header.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 { MonitorDetailsTabs } from './monitor_detials_tabs'; + +export const MonitorDetailsPageHeader = () => { + return ; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.tsx new file mode 100644 index 0000000000000..954ecc77c26c0 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page_title.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, { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useSelectedMonitor } from './hooks/use_selected_monitor'; +import { useSelectedLocation } from './hooks/use_selected_location'; +import { getMonitorRecentPingsAction, selectLatestPing, selectPingsLoading } from '../../state'; +import { MonitorSummaryLastRunInfo } from './last_run_info'; + +export const MonitorDetailsPageTitle = () => { + const dispatch = useDispatch(); + + const latestPing = useSelector(selectLatestPing); + const pingsLoading = useSelector(selectPingsLoading); + + const { monitorId } = useParams<{ monitorId: string }>(); + const { monitor } = useSelectedMonitor(); + const location = useSelectedLocation(); + + useEffect(() => { + const locationId = location?.label; + if (monitorId && locationId) { + dispatch(getMonitorRecentPingsAction.get({ monitorId, locationId })); + } + }, [dispatch, monitorId, location]); + + return ( + + {monitor?.name} + + {pingsLoading || (latestPing && latestPing.monitor.id !== monitorId) ? ( + + ) : latestPing ? ( + + ) : ( + + {i18n.translate('xpack.synthetics.monitorSummary.noLastRunInformationAvailable', { + defaultMessage: 'No last run information available for {location} yet.', + values: { + location: location?.label, + }, + })} + + )} + + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_tabs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx similarity index 83% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_tabs.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx index 725d209c8200e..6667034da5fdf 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_tabs.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx @@ -8,11 +8,11 @@ import { EuiIcon, EuiSpacer, EuiTabbedContent } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ErrorsTabContent } from './tabs_content/errors_tab_content'; -import { HistoryTabContent } from './tabs_content/history_tab_content'; -import { SummaryTabContent } from './tabs_content/summary_tab_content'; +import { ErrorsTabContent } from './monitor_errors/monitor_errors'; +import { HistoryTabContent } from './monitor_history/monitor_history'; +import { MonitorSummary } from './monitor_summary/monitor_summary'; -export const MonitorSummaryTabs = () => { +export const MonitorDetailsTabs = () => { const tabs = [ { id: 'summary', @@ -20,7 +20,7 @@ export const MonitorSummaryTabs = () => { content: ( <> - + ), }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/errors_tab_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/errors_tab_content.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/history_tab_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/history_tab_content.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/availability_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/availability_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/availability_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/availability_sparklines.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/duration_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/duration_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/duration_trend.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_trend.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/duration_trend.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_trend.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/kpi_wrapper.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/kpi_wrapper.tsx new file mode 100644 index 0000000000000..82a3eb3c6b7d8 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/kpi_wrapper.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 { css } from '@emotion/react'; +import { useEuiTheme } from '@elastic/eui'; + +export const KpiWrapper: React.FC> = ({ children }) => { + const { euiTheme } = useEuiTheme(); + + const wrapperStyle = css` + border: none; + & > span.euiLoadingSpinner { + margin: ${euiTheme.size.s}; + } + + .legacyMtrVis__container > div { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + `; + + return
{children}
; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx new file mode 100644 index 0000000000000..85adcd7ff3c0c --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_ten_test_runs.tsx @@ -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 React, { useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table'; +import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; + +import { ConfigKey, DataStream, JourneyStep, Ping } from '../../../../../../common/runtime_types'; +import { + formatTestDuration, + formatTestRunAt, +} from '../../../utils/monitor_test_result/test_time_formats'; +import { useSyntheticsSettingsContext } from '../../../contexts/synthetics_settings_context'; + +import { sortPings } from '../../../utils/monitor_test_result/sort_pings'; +import { checkIsStalePing } from '../../../utils/monitor_test_result/check_pings'; +import { selectPingsLoading, selectMonitorRecentPings, selectPingsError } from '../../../state'; +import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge'; +import { isStepEnd } from '../../common/monitor_test_result/browser_steps_list'; +import { JourneyStepScreenshotContainer } from '../../common/monitor_test_result/journey_step_screenshot_container'; + +import { useSelectedMonitor } from '../hooks/use_selected_monitor'; +import { useJourneySteps } from '../hooks/use_journey_steps'; + +type SortableField = 'timestamp' | 'monitor.status' | 'monitor.duration.us'; + +export const LastTenTestRuns = () => { + const { basePath } = useSyntheticsSettingsContext(); + + const [sortField, setSortField] = useState('timestamp'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + const pings = useSelector(selectMonitorRecentPings); + const sortedPings = useMemo(() => { + return sortPings(pings, sortField, sortDirection); + }, [pings, sortField, sortDirection]); + const pingsLoading = useSelector(selectPingsLoading); + const pingsError = useSelector(selectPingsError); + const { monitor } = useSelectedMonitor(); + + const isBrowserMonitor = monitor?.[ConfigKey.MONITOR_TYPE] === DataStream.BROWSER; + const hasStalePings = checkIsStalePing(monitor, pings?.[0]); + const loading = hasStalePings || pingsLoading; + + const sorting: EuiTableSortingType = { + sort: { + field: sortField as keyof Ping, + direction: sortDirection as 'asc' | 'desc', + }, + }; + + const handleTableChange = ({ page, sort }: Criteria) => { + if (sort !== undefined) { + setSortField(sort.field as SortableField); + setSortDirection(sort.direction); + } + }; + + const columns: Array> = [ + ...((isBrowserMonitor + ? [ + { + align: 'left', + field: 'timestamp', + name: SCREENSHOT_LABEL, + render: (_timestamp: string, item) => , + }, + ] + : []) as Array>), + { + align: 'left', + valign: 'middle', + field: 'timestamp', + name: '@timestamp', + sortable: true, + render: (timestamp: string, ping: Ping) => ( + + ), + }, + { + align: 'left', + valign: 'middle', + field: 'monitor.status', + name: RESULT_LABEL, + sortable: true, + render: (status: string) => , + }, + { + align: 'left', + field: 'error.message', + name: MESSAGE_LABEL, + textOnly: true, + render: (errorMessage: string) => ( + {errorMessage?.length > 0 ? errorMessage : '-'} + ), + }, + { + align: 'right', + valign: 'middle', + field: 'monitor.duration.us', + name: DURATION_LABEL, + sortable: true, + render: (durationUs: number) => {formatTestDuration(durationUs)}, + }, + ]; + + return ( + + + + +

{pings?.length >= 10 ? LAST_10_TEST_RUNS : TEST_RUNS}

+
+
+ + + + {i18n.translate('xpack.synthetics.monitorDetails.summary.viewHistory', { + defaultMessage: 'View History', + })} + + +
+ +
+ ); +}; + +const JourneyScreenshot = ({ ping }: { ping: Ping }) => { + const { data: stepsData, loading: stepsLoading } = useJourneySteps(ping?.monitor?.check_group); + const stepEnds: JourneyStep[] = (stepsData?.steps ?? []).filter(isStepEnd); + const stepLabels = stepEnds.map((stepEnd) => stepEnd?.synthetics?.step?.name ?? ''); + + const lastSignificantStep = useMemo(() => { + const copy = [...stepEnds]; + // Sort desc by timestamp + copy.sort( + (stepA, stepB) => + Number(new Date(stepB['@timestamp'])) - Number(new Date(stepA['@timestamp'])) + ); + return copy.find( + (stepEnd) => parseBadgeStatus(stepEnd?.synthetics?.step?.status ?? 'skipped') !== 'skipped' + ); + }, [stepEnds]); + + return ( + + ); +}; + +const TestDetailsLink = ({ + isBrowserMonitor, + timestamp, + ping, +}: { + isBrowserMonitor: boolean; + timestamp: string; + ping: Ping; +}) => { + const { euiTheme } = useEuiTheme(); + const { basePath } = useSyntheticsSettingsContext(); + + const timestampText = ( + + {formatTestRunAt(timestamp)} + + ); + + return isBrowserMonitor ? ( + + {timestampText} + + ) : ( + timestampText + ); +}; + +const TEST_RUNS = i18n.translate('xpack.synthetics.monitorDetails.summary.testRuns', { + defaultMessage: 'Test Runs', +}); + +const LAST_10_TEST_RUNS = i18n.translate( + 'xpack.synthetics.monitorDetails.summary.lastTenTestRuns', + { + defaultMessage: 'Last 10 Test Runs', + } +); + +const SCREENSHOT_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.screenshot', { + defaultMessage: 'Screenshot', +}); + +const RESULT_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.result', { + defaultMessage: 'Result', +}); + +const MESSAGE_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.message', { + defaultMessage: 'Message', +}); + +const DURATION_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.duration', { + defaultMessage: 'Duration', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx new file mode 100644 index 0000000000000..355dbfa19499e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingContent, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { useSelector } from 'react-redux'; +import { i18n } from '@kbn/i18n'; + +import { + ConfigKey, + DataStream, + EncryptedSyntheticsSavedMonitor, + Ping, +} from '../../../../../../common/runtime_types'; +import { checkIsStalePing } from '../../../utils/monitor_test_result/check_pings'; +import { formatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats'; + +import { useSyntheticsSettingsContext } from '../../../contexts'; +import { selectLatestPing, selectPingsLoading } from '../../../state'; +import { BrowserStepsList } from '../../common/monitor_test_result/browser_steps_list'; +import { SinglePingResult } from '../../common/monitor_test_result/single_ping_result'; +import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge'; + +import { useJourneySteps } from '../hooks/use_journey_steps'; +import { useSelectedMonitor } from '../hooks/use_selected_monitor'; + +export const LastTestRun = () => { + const { euiTheme } = useEuiTheme(); + const latestPing = useSelector(selectLatestPing); + const pingsLoading = useSelector(selectPingsLoading); + const { monitor } = useSelectedMonitor(); + + const { data: stepsData, loading: stepsLoading } = useJourneySteps( + latestPing?.monitor?.check_group + ); + + const hasStalePings = checkIsStalePing(monitor, latestPing); + const loading = hasStalePings || stepsLoading || pingsLoading; + + return ( + + + {!loading && latestPing?.error ? ( + + + {i18n.translate('xpack.synthetics.monitorDetails.summary.viewErrorDetails', { + defaultMessage: 'View error details', + })} + + + ) : null} + + + + {monitor?.type === DataStream.BROWSER ? ( + + ) : ( + + )} + + ); +}; + +const PanelHeader = ({ + monitor, + latestPing, + loading, +}: { + monitor: EncryptedSyntheticsSavedMonitor | null; + latestPing: Ping; + loading: boolean; +}) => { + const { euiTheme } = useEuiTheme(); + + const { basePath } = useSyntheticsSettingsContext(); + + const lastRunTimestamp = useMemo( + () => (latestPing?.timestamp ? formatTestRunAt(latestPing?.timestamp) : ''), + [latestPing?.timestamp] + ); + + const isBrowserMonitor = monitor?.[ConfigKey.MONITOR_TYPE] === DataStream.BROWSER; + + const TitleNode = ( + +

{LAST_TEST_RUN_LABEL}

+
+ ); + + if (loading) { + return ( + <> + + {TitleNode} + + + + + + + {isBrowserMonitor ? : null} + + + ); + } + + if (!latestPing) { + return <>{TitleNode}; + } + + return ( + <> + + {TitleNode} + + 0 ? 'fail' : 'success')} + /> + + + + {lastRunTimestamp} + + + + {isBrowserMonitor ? ( + + + {i18n.translate('xpack.synthetics.monitorDetails.summary.viewTestRun', { + defaultMessage: 'View test run', + })} + + + ) : null} + + + ); +}; + +const LAST_TEST_RUN_LABEL = i18n.translate( + 'xpack.synthetics.monitorDetails.summary.lastTestRunTitle', + { + defaultMessage: 'Last test run', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/locations_status.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/locations_status.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/locations_status.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/locations_status.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_details_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx similarity index 77% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_details_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx index 6209ddb7c5d2e..3d8013c79beff 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_details_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { css } from '@emotion/react'; import { EuiDescriptionList, EuiDescriptionListTitle, @@ -13,38 +14,46 @@ import { EuiBadge, EuiSpacer, EuiLink, - EuiLoadingSpinner, + EuiLoadingContent, + useEuiTheme, } from '@elastic/eui'; import { capitalize } from 'lodash'; import { i18n } from '@kbn/i18n'; import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; -import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { useSelectedMonitor } from '../hooks/use_selected_monitor'; import { MonitorTags } from './monitor_tags'; import { MonitorEnabled } from '../../monitors_page/management/monitor_list_table/monitor_enabled'; import { LocationsStatus } from './locations_status'; -import { - getSyntheticsMonitorAction, - selectMonitorStatus, - syntheticsMonitorSelector, -} from '../../../state/monitor_summary'; +import { getMonitorAction, selectLatestPing } from '../../../state'; import { ConfigKey } from '../../../../../../common/runtime_types'; export const MonitorDetailsPanel = () => { - const { data } = useSelector(selectMonitorStatus); + const { euiTheme } = useEuiTheme(); + const latestPing = useSelector(selectLatestPing); const { monitorId } = useParams<{ monitorId: string }>(); const dispatch = useDispatch(); - const { data: monitor, loading } = useSelector(syntheticsMonitorSelector); + const { monitor, loading } = useSelectedMonitor(); - if (!data) { - return ; + if ( + (latestPing && latestPing?.config_id !== monitorId) || + (monitor && monitor.id !== monitorId) + ) { + return ; } + const wrapperStyle = css` + .euiDescriptionList.euiDescriptionList--column > *, + .euiDescriptionList.euiDescriptionList--responsiveColumn > * { + margin-top: ${euiTheme.size.s}; + } + `; + return ( - +
{ENABLED_LABEL} @@ -55,14 +64,14 @@ export const MonitorDetailsPanel = () => { id={monitorId} monitor={monitor} reloadPage={() => { - dispatch(getSyntheticsMonitorAction.get(monitorId)); + dispatch(getMonitorAction.get({ monitorId })); }} /> )} {MONITOR_TYPE_LABEL} - {capitalize(data.monitor.type)} + {capitalize(monitor?.type)} {FREQUENCY_LABEL} Every 10 mins @@ -72,8 +81,8 @@ export const MonitorDetailsPanel = () => { {URL_LABEL} - - {data.url?.full} + + {latestPing?.url?.full} {TAGS_LABEL} @@ -81,17 +90,10 @@ export const MonitorDetailsPanel = () => { {monitor && } - +
); }; -const Wrapper = euiStyled.div` - .euiDescriptionList.euiDescriptionList--column > *, - .euiDescriptionList.euiDescriptionList--responsiveColumn > * { - margin-top: ${(props) => props.theme.eui.euiSizeS}; - } -`; - const FREQUENCY_LABEL = i18n.translate('xpack.synthetics.management.monitorList.frequency', { defaultMessage: 'Frequency', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx similarity index 89% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx index afe940fc06400..0349b3e96cea1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx @@ -23,8 +23,10 @@ import { AvailabilityPanel } from './availability_panel'; import { DurationPanel } from './duration_panel'; import { MonitorDetailsPanel } from './monitor_details_panel'; import { AvailabilitySparklines } from './availability_sparklines'; +import { LastTestRun } from './last_test_run'; +import { LastTenTestRuns } from './last_ten_test_runs'; -export const SummaryTabContent = () => { +export const MonitorSummary = () => { const { euiTheme } = useEuiTheme(); return ( @@ -57,7 +59,7 @@ export const SummaryTabContent = () => { {/* TODO: Add error metric and sparkline*/} - + @@ -79,17 +81,19 @@ export const SummaryTabContent = () => { - - {/* TODO: Add status panel*/} - + + {/* /!* TODO: Add status panel*!/ */} + {/* */} - {/* TODO: Add last run panel*/} + + + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_tags.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_tags.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/monitor_tags.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_tags.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/step_duration_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/step_duration_panel.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/step_duration_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/step_duration_panel.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/run_test_manually.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx similarity index 100% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/run_test_manually.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary.tsx deleted file mode 100644 index de5871304eccf..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { getSyntheticsMonitorAction, selectMonitorStatus } from '../../state/monitor_summary'; -import { useMonitorListBreadcrumbs } from '../monitors_page/hooks/use_breadcrumbs'; -export const MonitorSummaryPage = () => { - const { data } = useSelector(selectMonitorStatus); - - useMonitorListBreadcrumbs([{ text: data?.monitor.name ?? '' }]); - - const dispatch = useDispatch(); - - const { monitorId } = useParams<{ monitorId: string }>(); - - useEffect(() => { - dispatch(getSyntheticsMonitorAction.get(monitorId)); - }, [dispatch, monitorId]); - - return <>; -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_header_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_header_content.tsx deleted file mode 100644 index 1276de0c32974..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_header_content.tsx +++ /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 React from 'react'; -import { useSelector } from 'react-redux'; -import { MonitorSummaryTabs } from './monitor_summary_tabs'; -import { selectMonitorStatus } from '../../state/monitor_summary'; - -export const MonitorSummaryHeaderContent = () => { - const { data } = useSelector(selectMonitorStatus); - - if (!data) { - return <>; - } - - return ( - <> - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_title.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_title.tsx deleted file mode 100644 index 9e295496dd77c..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/monitor_summary_title.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React, { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { MonitorSummaryLastRunInfo } from './last_run_info'; -import { getMonitorStatusAction, selectMonitorStatus } from '../../state'; -import { RunTestManually } from './run_test_manually'; - -export const MonitorSummaryTitle = () => { - const dispatch = useDispatch(); - - const { data } = useSelector(selectMonitorStatus); - - const { monitorId } = useParams<{ monitorId: string }>(); - - useEffect(() => { - dispatch(getMonitorStatusAction.get({ monitorId, dateStart: 'now-30d', dateEnd: 'now' })); - }, [dispatch, monitorId]); - - return ( - - - - {data?.monitor.name} - - {data && } - - - - - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/kpi_wrapper.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/kpi_wrapper.tsx deleted file mode 100644 index 4496bc0031364..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/kpi_wrapper.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { euiStyled } from '@kbn/kibana-react-plugin/common'; - -export const KpiWrapper = euiStyled.div` - & .euiLoadingSpinner { - margin: ${({ theme }) => theme.eui.euiSizeS}; - } - - & .legacyMtrVis__container > div { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -`; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx index bf19c52b4c340..968d13df594dd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx @@ -13,6 +13,7 @@ import { EuiPanel, EuiSpacer, useEuiTheme, + useIsWithinMinBreakpoint, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ListFilters } from '../list_filters/list_filters'; @@ -25,7 +26,6 @@ import { EncryptedSyntheticsSavedMonitor, } from '../../../../../../../common/runtime_types'; import { SyntheticsSettingsContext } from '../../../../contexts/synthetics_settings_context'; -import { useBreakpoints } from '../../../../hooks'; import { getMonitorListColumns } from './columns'; import * as labels from './labels'; @@ -51,7 +51,7 @@ export const MonitorList = ({ errorSummaries, }: Props) => { const { basePath } = useContext(SyntheticsSettingsContext); - const isXl = useBreakpoints().up('xl'); + const isXl = useIsWithinMinBreakpoint('xxl'); const canEditSynthetics = useCanEditSynthetics(); const { euiTheme } = useEuiTheme(); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/empty_state/empty_state_error.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/empty_state/empty_state_error.tsx index 002b22f48b186..3f3a3552446f2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/empty_state/empty_state_error.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/empty_state/empty_state_error.tsx @@ -8,15 +8,15 @@ import React, { Fragment } from 'react'; import { EuiEmptyPrompt, EuiPanel, EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { IHttpSerializedFetchError } from '../../../../state'; interface EmptyStateErrorProps { - errors: Array>; + errors: IHttpSerializedFetchError[]; } export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { const unauthorized = errors.find( - (error) => error.message && error.message.includes('unauthorized') + (error) => error?.body?.message && error.body.message.includes('unauthorized') ); return ( @@ -47,11 +47,7 @@ export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { body={ {!unauthorized && - errors.map((error) => ( -

- {error.body?.message || error.message} -

- ))} + errors.map((error) =>

{error.body?.message}

)}
} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts index 35b9f786d4700..7ec5fccf3d6e4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts @@ -7,7 +7,6 @@ export * from './use_url_params'; export * from './use_breadcrumbs'; -export * from '../../../hooks/use_breakpoints'; export * from './use_service_allowed'; export * from './use_enablement'; export * from './use_locations'; @@ -15,3 +14,4 @@ export * from './use_last_x_checks'; export * from './use_last_50_duration_chart'; export * from './use_location_name'; export * from './use_status_by_location'; +export * from './use_composite_image'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.ts index 0902481c51a9c..4225f888a09bb 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_breadcrumbs.ts @@ -68,7 +68,8 @@ export const useBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { const params = useUrlParams()[0](); const kibana = useKibana(); const setBreadcrumbs = kibana.services.chrome?.setBreadcrumbs; - const uptimePath = kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? ''; + const syntheticsPath = + kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? ''; const observabilityPath = kibana.services.application?.getUrlForApp('observability-overview') ?? ''; const navigate = kibana.services.application?.navigateToUrl; @@ -77,10 +78,10 @@ export const useBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { if (setBreadcrumbs) { setBreadcrumbs( handleBreadcrumbClick( - makeBaseBreadcrumb(uptimePath, observabilityPath, params).concat(extraCrumbs), + makeBaseBreadcrumb(syntheticsPath, observabilityPath, params).concat(extraCrumbs), navigate ) ); } - }, [uptimePath, observabilityPath, extraCrumbs, navigate, params, setBreadcrumbs]); + }, [syntheticsPath, observabilityPath, extraCrumbs, navigate, params, setBreadcrumbs]); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.test.tsx new file mode 100644 index 0000000000000..f77d169dc739e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.test.tsx @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as redux from 'react-redux'; +import { renderHook } from '@testing-library/react-hooks'; +import { ScreenshotRefImageData, ScreenshotBlockCache } from '../../../../common/runtime_types'; +import { fetchBlocksAction } from '../state'; +import { shouldCompose, useCompositeImage } from './use_composite_image'; +import * as compose from '../utils/monitor_test_result/compose_screenshot_images'; + +const MIME = 'image/jpeg'; + +describe('use composite image', () => { + let imageData: string | undefined; + let imgRef: ScreenshotRefImageData; + let curRef: ScreenshotRefImageData; + let blocks: ScreenshotBlockCache; + + beforeEach(() => { + imgRef = { + stepName: 'step-1', + maxSteps: 3, + ref: { + screenshotRef: { + '@timestamp': '123', + monitor: { + check_group: 'check-group', + }, + screenshot_ref: { + width: 100, + height: 200, + blocks: [ + { + hash: 'hash1', + top: 0, + left: 0, + width: 10, + height: 10, + }, + { + hash: 'hash2', + top: 0, + left: 10, + width: 10, + height: 10, + }, + ], + }, + synthetics: { + package_version: 'v1', + step: { index: 0, name: 'first' }, + type: 'step/screenshot_ref', + }, + }, + }, + }; + curRef = { + stepName: 'step-1', + maxSteps: 3, + ref: { + screenshotRef: { + '@timestamp': '234', + monitor: { + check_group: 'check-group-2', + }, + screenshot_ref: { + width: 100, + height: 200, + blocks: [ + { + hash: 'hash1', + top: 0, + left: 0, + width: 10, + height: 10, + }, + { + hash: 'hash2', + top: 0, + left: 10, + width: 10, + height: 10, + }, + ], + }, + synthetics: { + package_version: 'v1', + step: { index: 1, name: 'second' }, + type: 'step/screenshot_ref', + }, + }, + }, + }; + blocks = { + hash1: { + id: 'id1', + synthetics: { + blob: 'blob', + blob_mime: MIME, + }, + }, + hash2: { + id: 'id2', + synthetics: { + blob: 'blob', + blob_mime: MIME, + }, + }, + }; + }); + + describe('shouldCompose', () => { + it('returns true if all blocks are loaded and ref is new', () => { + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(true); + }); + + it('returns false if a required block is pending', () => { + blocks.hash2 = { status: 'pending' }; + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(false); + }); + + it('returns false if a required block is missing', () => { + delete blocks.hash2; + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(false); + }); + + it('returns false if imageData is defined and the refs have matching step index/check_group', () => { + imageData = 'blob'; + curRef.ref.screenshotRef.synthetics.step.index = 0; + curRef.ref.screenshotRef.monitor.check_group = 'check-group'; + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(false); + }); + + it('returns true if imageData is defined and the refs have different step names', () => { + imageData = 'blob'; + curRef.ref.screenshotRef.synthetics.step.index = 0; + expect(shouldCompose(imageData, imgRef, curRef, blocks)).toBe(true); + }); + }); + + describe('useCompositeImage', () => { + let useDispatchMock: jest.Mock; + let canvasMock: unknown; + let removeChildSpy: jest.Mock; + let selectorSpy: jest.SpyInstance; + let composeSpy: jest.SpyInstance; + + beforeEach(() => { + useDispatchMock = jest.fn(); + removeChildSpy = jest.fn(); + canvasMock = { + parentElement: { + removeChild: removeChildSpy, + }, + toDataURL: jest.fn().mockReturnValue('compose success'), + }; + // @ts-expect-error mocking canvas element for testing + jest.spyOn(document, 'createElement').mockReturnValue(canvasMock); + jest.spyOn(redux, 'useDispatch').mockReturnValue(useDispatchMock); + selectorSpy = jest.spyOn(redux, 'useSelector').mockReturnValue({ blocks }); + composeSpy = jest + .spyOn(compose, 'composeScreenshotRef') + .mockReturnValue(new Promise((r) => r([]))); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('does not compose if all blocks are not loaded', () => { + blocks = {}; + renderHook(() => useCompositeImage(imgRef, jest.fn(), imageData)); + + expect(useDispatchMock).toHaveBeenCalledWith(fetchBlocksAction(['hash1', 'hash2'])); + }); + + it('composes when all required blocks are loaded', async () => { + const onComposeImageSuccess = jest.fn(); + const { waitFor } = renderHook(() => useCompositeImage(imgRef, onComposeImageSuccess)); + + expect(selectorSpy).toHaveBeenCalled(); + expect(composeSpy).toHaveBeenCalledTimes(1); + expect(composeSpy.mock.calls[0][0]).toEqual(imgRef); + expect(composeSpy.mock.calls[0][1]).toBe(canvasMock); + expect(composeSpy.mock.calls[0][2]).toBe(blocks); + + await waitFor( + () => { + expect(onComposeImageSuccess).toHaveBeenCalledTimes(1); + expect(onComposeImageSuccess).toHaveBeenCalledWith('compose success'); + }, + { timeout: 10000 } + ); + }); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.ts new file mode 100644 index 0000000000000..5cdaffb83fa21 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_composite_image.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useDispatch, useSelector } from 'react-redux'; +import React from 'react'; +import { composeScreenshotRef } from '../utils/monitor_test_result/compose_screenshot_images'; +import { + ScreenshotRefImageData, + ScreenshotBlockCache, + StoreScreenshotBlock, +} from '../../../../common/runtime_types'; +import { fetchBlocksAction, isPendingBlock } from '../state'; +import { selectBrowserJourneyState } from '../state'; + +function allBlocksLoaded(blocks: { [key: string]: StoreScreenshotBlock }, hashes: string[]) { + for (const hash of hashes) { + if (!blocks[hash] || isPendingBlock(blocks[hash])) { + return false; + } + } + return true; +} + +/** + * Checks if two refs are the same. If the ref is unchanged, there's no need + * to run the expensive draw procedure. + * + * The key fields here are `step.index` and `check_group`, as there's a 1:1 between + * journey and check group, and each step has a unique index within a journey. + */ +const isNewRef = ( + { + ref: { + screenshotRef: { + synthetics: { + step: { index: indexA }, + }, + monitor: { check_group: checkGroupA }, + }, + }, + }: ScreenshotRefImageData, + { + ref: { + screenshotRef: { + synthetics: { + step: { index: indexB }, + }, + monitor: { check_group: checkGroupB }, + }, + }, + }: ScreenshotRefImageData +): boolean => indexA !== indexB || checkGroupA !== checkGroupB; + +export function shouldCompose( + imageData: string | undefined, + imgRef: ScreenshotRefImageData, + curRef: ScreenshotRefImageData, + blocks: ScreenshotBlockCache +): boolean { + return ( + allBlocksLoaded( + blocks, + imgRef.ref.screenshotRef.screenshot_ref.blocks.map(({ hash }) => hash) + ) && + (typeof imageData === 'undefined' || isNewRef(imgRef, curRef)) + ); +} + +/** + * Assembles the data for a composite image and returns the composite to a callback. + * @param imgRef the data and dimensions for the composite image. + * @param onComposeImageSuccess sends the composited image to this callback. + * @param imageData this is the composited image value, if it is truthy the function will skip the compositing process + */ +export const useCompositeImage = ( + imgRef: ScreenshotRefImageData, + onComposeImageSuccess: React.Dispatch, + imageData?: string +): void => { + const dispatch = useDispatch(); + const { blocks }: { blocks: ScreenshotBlockCache } = useSelector(selectBrowserJourneyState); + + React.useEffect(() => { + dispatch( + fetchBlocksAction(imgRef.ref.screenshotRef.screenshot_ref.blocks.map(({ hash }) => hash)) + ); + }, [dispatch, imgRef.ref.screenshotRef.screenshot_ref.blocks]); + + const [curRef, setCurRef] = React.useState(imgRef); + + React.useEffect(() => { + const canvas = document.createElement('canvas'); + + async function compose() { + await composeScreenshotRef(imgRef, canvas, blocks); + const imgData = canvas.toDataURL('image/jpg', 1.0); + onComposeImageSuccess(imgData); + } + + // if the URL is truthy it means it's already been composed, so there + // is no need to call the function + if (shouldCompose(imageData, imgRef, curRef, blocks)) { + compose(); + setCurRef(imgRef); + } + return () => { + canvas.parentElement?.removeChild(canvas); + }; + }, [blocks, curRef, imageData, imgRef, onComposeImageSuccess]); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index 8da37518fbede..3c1081f9f1f04 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -17,9 +17,9 @@ import { useInspectorContext } from '@kbn/observability-plugin/public'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; import { MonitorAddPage } from './components/monitor_add_edit/monitor_add_page'; import { MonitorEditPage } from './components/monitor_add_edit/monitor_edit_page'; -import { MonitorSummaryHeaderContent } from './components/monitor_summary/monitor_summary_header_content'; -import { MonitorSummaryTitle } from './components/monitor_summary/monitor_summary_title'; -import { MonitorSummaryPage } from './components/monitor_summary/monitor_summary'; +import { MonitorDetailsPageHeader } from './components/monitor_details/monitor_details_page_header'; +import { MonitorDetailsPageTitle } from './components/monitor_details/monitor_details_page_title'; +import { MonitorDetailsPage } from './components/monitor_details/monitor_details_page'; import { GettingStartedPage } from './components/getting_started/getting_started_page'; import { MonitorsPageHeader } from './components/monitors_page/management/page_header/monitors_page_header'; import { OverviewPage } from './components/monitors_page/overview/overview_page'; @@ -78,16 +78,16 @@ const getRoutes = ( }, }, { - title: i18n.translate('xpack.synthetics.monitorSummaryRoute.title', { - defaultMessage: 'Monitor summary | {baseTitle}', + title: i18n.translate('xpack.synthetics.monitorDetails.title', { + defaultMessage: 'Synthetics Monitor Details | {baseTitle}', values: { baseTitle }, }), path: MONITOR_ROUTE, - component: () => , - dataTestSubj: 'syntheticsGettingStartedPage', + component: () => , + dataTestSubj: 'syntheticsMonitorDetailsPage', pageHeader: { - children: , - pageTitle: , + children: , + pageTitle: , // rightSideItems: [], }, }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/actions.ts new file mode 100644 index 0000000000000..3a151ced4246c --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/actions.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 { createAction } from '@reduxjs/toolkit'; +import { PutBlocksPayload } from './models'; + +// This action denotes a set of blocks is required +export const fetchBlocksAction = createAction('[BROWSER JOURNEY] FETCH BLOCKS'); + +// This action denotes a request for a set of blocks is in flight +export const setBlockLoadingAction = createAction( + '[BROWSER JOURNEY] SET BLOCKS IN FLIGHT' +); + +// Block data has been received, and should be added to the store +export const putBlocksAction = createAction( + '[BROWSER JOURNEY] PUT SCREENSHOT BLOCKS' +); + +// Updates the total size of the image blob data cached in the store +export const putCacheSize = createAction('[BROWSER JOURNEY] PUT CACHE SIZE'); + +// Keeps track of the most-requested blocks +export const updateHitCountsAction = createAction('[BROWSER JOURNEY] UPDATE HIT COUNTS'); + +// Reduce the cache size to the value in the action payload +export const pruneCacheAction = createAction( + '[BROWSER JOURNEY] PRUNE SCREENSHOT BLOCK CACHE' +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts new file mode 100644 index 0000000000000..a6fd6185af06b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { apiService } from '../../../../utils/api_service'; +import { + FailedStepsApiResponse, + FailedStepsApiResponseType, + ScreenshotBlockDoc, + ScreenshotImageBlob, + ScreenshotRefImageData, + SyntheticsJourneyApiResponse, + SyntheticsJourneyApiResponseType, + Ping, + PingType, +} from '../../../../../common/runtime_types'; +import { API_URLS } from '../../../../../common/constants'; + +export interface FetchJourneyStepsParams { + checkGroup: string; + syntheticEventTypes?: string[]; +} + +export async function fetchScreenshotBlockSet(params: string[]): Promise { + return apiService.post(API_URLS.JOURNEY_SCREENSHOT_BLOCKS, { + hashes: params, + }); +} + +export async function fetchJourneySteps( + params: FetchJourneyStepsParams +): Promise { + return apiService.get( + API_URLS.JOURNEY.replace('{checkGroup}', params.checkGroup), + { syntheticEventTypes: params.syntheticEventTypes }, + SyntheticsJourneyApiResponseType + ); +} + +export async function fetchJourneysFailedSteps({ + checkGroups, +}: { + checkGroups: string[]; +}): Promise { + return apiService.get(API_URLS.JOURNEY_FAILED_STEPS, { checkGroups }, FailedStepsApiResponseType); +} + +export async function fetchLastSuccessfulCheck({ + monitorId, + timestamp, + stepIndex, + location, +}: { + monitorId: string; + timestamp: string; + stepIndex: number; + location?: string; +}): Promise { + return await apiService.get( + API_URLS.SYNTHETICS_SUCCESSFUL_CHECK, + { + monitorId, + timestamp, + stepIndex, + location, + }, + PingType + ); +} + +export async function getJourneyScreenshot( + imgSrc: string +): Promise { + try { + const imgRequest = new Request(imgSrc); + + const response = await fetch(imgRequest); + + if (response.status !== 200) { + return null; + } + + const contentType = response.headers.get('content-type'); + const stepName = response.headers.get('caption-name'); + const maxSteps = Number(response.headers.get('max-steps') ?? 0); + if (contentType?.indexOf('application/json') !== -1) { + return { + stepName, + maxSteps, + ref: await response.json(), + }; + } else { + return { + stepName, + maxSteps, + src: URL.createObjectURL(await response.blob()), + }; + } + } catch (e) { + return null; + } +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/effects.ts new file mode 100644 index 0000000000000..ad85440ee9708 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/effects.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 { Action } from 'redux-actions'; +import { all, call, fork, put, select, takeEvery, throttle } from 'redux-saga/effects'; +import { ScreenshotBlockDoc, ScreenshotBlockCache } from '../../../../../common/runtime_types'; +import { fetchScreenshotBlockSet } from './api'; + +import { + fetchBlocksAction, + setBlockLoadingAction, + pruneCacheAction, + putBlocksAction, + putCacheSize, + updateHitCountsAction, +} from './actions'; + +import { isPendingBlock } from './models'; + +import { selectBrowserJourneyState } from './selectors'; + +export function* browserJourneyEffects() { + yield all([fork(fetchScreenshotBlocks), fork(generateBlockStatsOnPut), fork(pruneBlockCache)]); +} + +function* fetchBlocks(hashes: string[]) { + yield put(setBlockLoadingAction(hashes)); + const blocks: ScreenshotBlockDoc[] = yield call(fetchScreenshotBlockSet, hashes); + yield put(putBlocksAction({ blocks })); +} + +function* fetchScreenshotBlocks() { + /** + * We maintain a list of each hash and how many times it is requested so we can avoid + * subsequent re-requests if the block is dropped due to cache pruning. + */ + yield takeEvery(String(fetchBlocksAction), function* (action: Action) { + if (action.payload.length > 0) { + yield put(updateHitCountsAction(action.payload)); + } + }); + + /** + * We do a short delay to allow multiple item renders to queue up before dispatching + * a fetch to the backend. + */ + yield throttle(20, String(fetchBlocksAction), function* () { + const { blocks }: { blocks: ScreenshotBlockCache } = yield select(selectBrowserJourneyState); + const toFetch = Object.keys(blocks).filter((hash) => { + const block = blocks[hash]; + return isPendingBlock(block) && block.status !== 'loading'; + }); + + if (toFetch.length > 0) { + yield fork(fetchBlocks, toFetch); + } + }); +} + +function* generateBlockStatsOnPut() { + yield takeEvery( + String(putBlocksAction), + function* (action: Action<{ blocks: ScreenshotBlockDoc[] }>) { + const batchSize = action.payload.blocks.reduce((total, cur) => { + return cur.synthetics.blob.length + total; + }, 0); + yield put(putCacheSize(batchSize)); + } + ); +} + +// 4 MB cap for cache size +const MAX_CACHE_SIZE = 4000000; + +function* pruneBlockCache() { + yield takeEvery(String(putCacheSize), function* (_action: Action) { + const { cacheSize }: { cacheSize: number } = yield select(selectBrowserJourneyState); + + if (cacheSize > MAX_CACHE_SIZE) { + yield put(pruneCacheAction(cacheSize - MAX_CACHE_SIZE)); + } + }); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/index.ts new file mode 100644 index 0000000000000..75b82f989358b --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/index.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 { createReducer } from '@reduxjs/toolkit'; + +import { isScreenshotBlockDoc } from '../../../../../common/runtime_types'; + +import type { BrowserJourneyState } from './models'; +import { + pruneCacheAction, + putBlocksAction, + putCacheSize, + updateHitCountsAction, + fetchBlocksAction, + setBlockLoadingAction, +} from './actions'; + +const initialState: BrowserJourneyState = { + blocks: {}, + cacheSize: 0, + hitCount: [], +}; + +export const browserJourneyReducer = createReducer(initialState, (builder) => { + builder + /** + * When removing blocks from the cache, we receive an action with a number. + * The number equates to the desired ceiling size of the cache. We then discard + * blocks, ordered by the least-requested. We continue dropping blocks until + * the newly-pruned size will be less than the ceiling supplied by the action. + */ + .addCase(pruneCacheAction, (state, action) => { + handlePruneAction(state, action.payload); + }) + + /** + * Keep track of the least- and most-requested blocks, so when it is time to + * prune we keep the most commonly-used ones. + */ + .addCase(updateHitCountsAction, (state, action) => { + handleUpdateHitCountsAction(state, action.payload); + }) + + .addCase(putCacheSize, (state, action) => { + state.cacheSize = state.cacheSize + action.payload; + }) + + .addCase(fetchBlocksAction, (state, action) => { + state.blocks = { + ...state.blocks, + ...action.payload + // there's no need to overwrite existing blocks because the key + // is either storing a pending req or a cached result + .filter((b) => !state.blocks[b]) + // convert the list of new hashes in the payload to an object that + // will combine with with the existing blocks cache + .reduce( + (acc, cur) => ({ + ...acc, + [cur]: { status: 'pending' }, + }), + {} + ), + }; + }) + + .addCase(setBlockLoadingAction, (state, action) => { + state.blocks = { + ...state.blocks, + ...action.payload.reduce( + (acc, cur) => ({ + ...acc, + [cur]: { status: 'loading' }, + }), + {} + ), + }; + }) + + .addCase(putBlocksAction, (state, action) => { + state.blocks = { + ...state.blocks, + ...action.payload.blocks.reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {}), + }; + }); +}); + +function handlePruneAction(state: BrowserJourneyState, pruneSize: number) { + const { blocks, hitCount } = state; + const hashesToPrune: string[] = []; + let sizeToRemove = 0; + let removeIndex = hitCount.length - 1; + while (sizeToRemove < pruneSize && removeIndex >= 0) { + const { hash } = hitCount[removeIndex]; + removeIndex--; + if (!blocks[hash]) continue; + const block = blocks[hash]; + if (isScreenshotBlockDoc(block)) { + sizeToRemove += block.synthetics.blob.length; + hashesToPrune.push(hash); + } + } + for (const hash of hashesToPrune) { + delete blocks[hash]; + } + + state.cacheSize = state.cacheSize - sizeToRemove; + state.hitCount = hitCount.slice(0, removeIndex + 1); +} + +function handleUpdateHitCountsAction(state: BrowserJourneyState, hashes: string[]) { + const newHitCount = [...state.hitCount]; + const hitTime = Date.now(); + hashes.forEach((hash) => { + const countItem = newHitCount.find((item) => item.hash === hash); + if (!countItem) { + newHitCount.push({ hash, hitTime }); + } else { + countItem.hitTime = hitTime; + } + }); + // sorts in descending order + newHitCount.sort((a, b) => b.hitTime - a.hitTime); + + state.hitCount = newHitCount; +} + +export * from './api'; +export * from './models'; +export * from './actions'; +export * from './effects'; +export * from './selectors'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/models.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/models.ts new file mode 100644 index 0000000000000..12d6074300c89 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/models.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 { + PendingBlock, + ScreenshotBlockCache, + ScreenshotBlockDoc, +} from '../../../../../common/runtime_types'; + +export function isPendingBlock(data: unknown): data is PendingBlock { + return ['pending', 'loading'].some((s) => s === (data as PendingBlock)?.status); +} + +export interface CacheHitCount { + hash: string; + hitTime: number; +} + +export interface BrowserJourneyState { + blocks: ScreenshotBlockCache; + cacheSize: number; + hitCount: CacheHitCount[]; +} + +export interface PutBlocksPayload { + blocks: ScreenshotBlockDoc[]; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/selectors.ts new file mode 100644 index 0000000000000..eae2632d9ae5a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/selectors.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SyntheticsAppState } from '../root_reducer'; + +export const selectBrowserJourneyState = (state: SyntheticsAppState) => state.browserJourney; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts index 6076292c34550..727fd0dfcd4c2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts @@ -15,5 +15,6 @@ export * from './index_status'; export * from './synthetics_enablement'; export * from './service_locations'; export * from './monitor_list'; -export * from './monitor_summary'; +export * from './monitor_details'; export * from './overview'; +export * from './browser_journey'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts index d1592e26bf17d..f5351c65d0d6b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; import { createReducer } from '@reduxjs/toolkit'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; import { StatesIndexStatus } from '../../../../../common/runtime_types'; import { getIndexStatus, getIndexStatusSuccess, getIndexStatusFail } from './actions'; @@ -14,7 +14,7 @@ import { getIndexStatus, getIndexStatusSuccess, getIndexStatusFail } from './act export interface IndexStatusState { data: StatesIndexStatus | null; loading: boolean; - error: IHttpFetchError | null; + error: IHttpSerializedFetchError | null; } const initialState: IndexStatusState = { @@ -33,7 +33,7 @@ export const indexStatusReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getIndexStatusFail, (state, action) => { - state.error = action.payload as IHttpFetchError; + state.error = serializeHttpFetchError(action.payload); state.loading = false; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/actions.ts new file mode 100644 index 0000000000000..a80196275a759 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/actions.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createAction } from '@reduxjs/toolkit'; +import { + Ping, + PingsResponse, + EncryptedSyntheticsSavedMonitor, +} from '../../../../../common/runtime_types'; +import { QueryParams } from './api'; +import { createAsyncAction } from '../utils/actions'; + +export const setMonitorDetailsLocationAction = createAction( + '[MONITOR SUMMARY] SET LOCATION' +); + +export const getMonitorStatusAction = createAsyncAction('[MONITOR DETAILS] GET'); + +export const getMonitorAction = createAsyncAction< + { monitorId: string }, + EncryptedSyntheticsSavedMonitor +>('[MONITOR DETAILS] GET MONITOR'); + +export const getMonitorRecentPingsAction = createAsyncAction< + { monitorId: string; locationId: string }, + PingsResponse +>('[MONITOR DETAILS] GET RECENT PINGS'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/api.ts new file mode 100644 index 0000000000000..f2541b119e56d --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/api.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 { SavedObject } from '@kbn/core/types'; +import { apiService } from '../../../../utils/api_service'; +import { + EncryptedSyntheticsSavedMonitor, + PingsResponse, + PingsResponseType, + SyntheticsMonitor, +} from '../../../../../common/runtime_types'; +import { API_URLS, SYNTHETICS_API_URLS } from '../../../../../common/constants'; + +export interface QueryParams { + monitorId: string; + dateStart: string; + dateEnd: string; +} + +export const fetchMonitorRecentPings = async ({ + monitorId, + locationId, +}: { + monitorId: string; + locationId: string; +}): Promise => { + const from = new Date(0).toISOString(); + const to = new Date().toISOString(); + const locations = JSON.stringify([locationId]); + const sort = 'desc'; + const size = 10; + + return await apiService.get( + SYNTHETICS_API_URLS.PINGS, + { monitorId, from, to, locations, sort, size }, + PingsResponseType + ); +}; + +export const fetchSyntheticsMonitor = async ({ + monitorId, +}: { + monitorId: string; +}): Promise => { + const savedObject = (await apiService.get( + `${API_URLS.SYNTHETICS_MONITORS}/${monitorId}` + )) as SavedObject; + + return { + id: savedObject.id, + ...savedObject.attributes, + updated_at: savedObject.updated_at, + } as EncryptedSyntheticsSavedMonitor; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/effects.ts new file mode 100644 index 0000000000000..1b1b686400d88 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/effects.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 { takeLeading } from 'redux-saga/effects'; +import { fetchEffectFactory } from '../utils/fetch_effect'; +import { getMonitorRecentPingsAction, getMonitorAction } from './actions'; +import { fetchSyntheticsMonitor, fetchMonitorRecentPings } from './api'; + +export function* fetchSyntheticsMonitorEffect() { + yield takeLeading( + getMonitorRecentPingsAction.get, + fetchEffectFactory( + fetchMonitorRecentPings, + getMonitorRecentPingsAction.success, + getMonitorRecentPingsAction.fail + ) + ); + + yield takeLeading( + getMonitorAction.get, + fetchEffectFactory(fetchSyntheticsMonitor, getMonitorAction.success, getMonitorAction.fail) + ); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts new file mode 100644 index 0000000000000..a2d9379df778e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; +import { createReducer } from '@reduxjs/toolkit'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { + getMonitorRecentPingsAction, + setMonitorDetailsLocationAction, + getMonitorAction, +} from './actions'; +import { EncryptedSyntheticsSavedMonitor, Ping } from '../../../../../common/runtime_types'; + +export interface MonitorDetailsState { + pings: Ping[]; + loading: boolean; + syntheticsMonitorLoading: boolean; + syntheticsMonitor: EncryptedSyntheticsSavedMonitor | null; + error: IHttpSerializedFetchError | null; + selectedLocationId: string | null; +} + +const initialState: MonitorDetailsState = { + pings: [], + loading: false, + syntheticsMonitor: null, + syntheticsMonitorLoading: false, + error: null, + selectedLocationId: null, +}; + +export const monitorDetailsReducer = createReducer(initialState, (builder) => { + builder + .addCase(setMonitorDetailsLocationAction, (state, action) => { + state.selectedLocationId = action.payload; + }) + + .addCase(getMonitorRecentPingsAction.get, (state) => { + state.loading = true; + }) + .addCase(getMonitorRecentPingsAction.success, (state, action) => { + state.pings = action.payload.pings; + state.loading = false; + }) + .addCase(getMonitorRecentPingsAction.fail, (state, action) => { + state.error = serializeHttpFetchError(action.payload as IHttpFetchError); + state.loading = false; + }) + + .addCase(getMonitorAction.get, (state) => { + state.syntheticsMonitorLoading = true; + }) + .addCase(getMonitorAction.success, (state, action) => { + state.syntheticsMonitor = action.payload; + state.syntheticsMonitorLoading = false; + }) + .addCase(getMonitorAction.fail, (state, action) => { + state.error = serializeHttpFetchError(action.payload as IHttpFetchError); + state.syntheticsMonitorLoading = false; + }); +}); + +export * from './actions'; +export * from './effects'; +export * from './selectors'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts similarity index 53% rename from x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/selectors.ts rename to x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts index d361024e839f2..5c6ba75e8cd6d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/selectors.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts @@ -8,13 +8,19 @@ import { createSelector } from 'reselect'; import type { SyntheticsAppState } from '../root_reducer'; -const getState = (appState: SyntheticsAppState) => appState.monitorStatus; +const getState = (appState: SyntheticsAppState) => appState.monitorDetails; + +export const selectorMonitorDetailsState = createSelector(getState, (state) => state); export const selectSelectedLocationId = createSelector( getState, (state) => state.selectedLocationId ); -export const selectMonitorStatus = createSelector(getState, (state) => state); +export const selectLatestPing = createSelector(getState, (state) => state.pings?.[0] ?? null); + +export const selectPingsLoading = createSelector(getState, (state) => state.loading); + +export const selectMonitorRecentPings = createSelector(getState, (state) => state.pings); -export const syntheticsMonitorSelector = (state: SyntheticsAppState) => state.syntheticsMonitor; +export const selectPingsError = createSelector(getState, (state) => state.error); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/actions.ts deleted file mode 100644 index 9595243a53bc3..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/actions.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createAction } from '@reduxjs/toolkit'; -import { Ping, SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { QueryParams } from './api'; -import { createAsyncAction } from '../utils/actions'; - -export const setMonitorSummaryLocationAction = createAction( - '[MONITOR SUMMARY] SET LOCATION' -); - -export const getMonitorStatusAction = createAsyncAction('[MONITOR SUMMARY] GET'); - -export const getSyntheticsMonitorAction = createAsyncAction( - 'fetchSyntheticsMonitorAction' -); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/api.ts deleted file mode 100644 index af01acf97592d..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/api.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 { SavedObject } from '@kbn/core/types'; -import { apiService } from '../../../../utils/api_service'; -import { Ping, SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { API_URLS, SYNTHETICS_API_URLS } from '../../../../../common/constants'; - -export interface QueryParams { - monitorId: string; - dateStart: string; - dateEnd: string; -} - -export const fetchMonitorStatus = async (params: QueryParams): Promise => { - return await apiService.get(SYNTHETICS_API_URLS.MONITOR_STATUS, { ...params }); -}; - -export const fetchSyntheticsMonitor = async (monitorId: string): Promise => { - const { attributes } = (await apiService.get( - `${API_URLS.SYNTHETICS_MONITORS}/${monitorId}` - )) as SavedObject; - - return attributes; -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/effects.ts deleted file mode 100644 index 9a1b52e1e24df..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/effects.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 { takeLeading } from 'redux-saga/effects'; -import { fetchEffectFactory } from '../utils/fetch_effect'; -import { getMonitorStatusAction, getSyntheticsMonitorAction } from './actions'; -import { fetchMonitorStatus, fetchSyntheticsMonitor } from './api'; - -export function* fetchMonitorStatusEffect() { - yield takeLeading( - getMonitorStatusAction.get, - fetchEffectFactory( - fetchMonitorStatus, - getMonitorStatusAction.success, - getMonitorStatusAction.fail - ) - ); -} - -export function* fetchSyntheticsMonitorEffect() { - yield takeLeading( - getSyntheticsMonitorAction.get, - fetchEffectFactory( - fetchSyntheticsMonitor, - getSyntheticsMonitorAction.success, - getSyntheticsMonitorAction.fail - ) - ); -} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/index.ts deleted file mode 100644 index 04941c6286211..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createReducer } from '@reduxjs/toolkit'; -import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; - -import { getMonitorStatusAction, setMonitorSummaryLocationAction } from './actions'; -import type { Ping } from '../../../../../common/runtime_types'; - -export interface MonitorSummaryState { - data: Ping | null; - loading: boolean; - error: IHttpFetchError | null; - selectedLocationId: string | null; -} - -const initialState: MonitorSummaryState = { - data: null, - loading: false, - error: null, - selectedLocationId: null, -}; - -export const monitorStatusReducer = createReducer(initialState, (builder) => { - builder - .addCase(setMonitorSummaryLocationAction, (state, action) => { - state.selectedLocationId = action.payload; - }) - .addCase(getMonitorStatusAction.get, (state) => { - state.loading = true; - }) - .addCase(getMonitorStatusAction.success, (state, action) => { - state.data = action.payload; - state.loading = false; - }) - .addCase(getMonitorStatusAction.fail, (state, action) => { - state.error = action.payload as IHttpFetchError; - state.loading = false; - }); -}); - -export * from './actions'; -export * from './effects'; -export * from './selectors'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/synthetics_montior_reducer.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/synthetics_montior_reducer.ts deleted file mode 100644 index 6fa133842f4fc..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_summary/synthetics_montior_reducer.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createReducer } from '@reduxjs/toolkit'; -import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; - -import type { SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { getSyntheticsMonitorAction } from './actions'; - -export interface SyntheticsMonitorState { - data: SyntheticsMonitor | null; - loading: boolean; - error: IHttpFetchError | null; -} - -const initialState: SyntheticsMonitorState = { - data: null, - loading: false, - error: null, -}; - -export const syntheticsMonitorReducer = createReducer(initialState, (builder) => { - builder - .addCase(getSyntheticsMonitorAction.get, (state) => { - state.loading = true; - }) - .addCase(getSyntheticsMonitorAction.success, (state, action) => { - state.data = action.payload; - state.loading = false; - }) - .addCase(getSyntheticsMonitorAction.fail, (state, action) => { - state.error = action.payload as IHttpFetchError; - state.loading = false; - }); -}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts index 389b21ea5ea1b..1345f6c2b4c39 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts @@ -6,12 +6,13 @@ */ import { all, fork } from 'redux-saga/effects'; -import { fetchMonitorStatusEffect, fetchSyntheticsMonitorEffect } from './monitor_summary'; +import { fetchSyntheticsMonitorEffect } from './monitor_details'; import { fetchIndexStatusEffect } from './index_status'; import { fetchSyntheticsEnablementEffect } from './synthetics_enablement'; import { fetchMonitorListEffect, upsertMonitorEffect } from './monitor_list'; import { fetchMonitorOverviewEffect, quietFetchOverviewEffect } from './overview'; import { fetchServiceLocationsEffect } from './service_locations'; +import { browserJourneyEffects } from './browser_journey'; export const rootEffect = function* root(): Generator { yield all([ @@ -20,9 +21,9 @@ export const rootEffect = function* root(): Generator { fork(upsertMonitorEffect), fork(fetchServiceLocationsEffect), fork(fetchMonitorListEffect), - fork(fetchMonitorStatusEffect), fork(fetchSyntheticsMonitorEffect), fork(fetchMonitorOverviewEffect), fork(quietFetchOverviewEffect), + fork(browserJourneyEffects), ]); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts index 34e036bf6fac4..c83605ffad1f8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts @@ -7,17 +7,15 @@ import { combineReducers } from '@reduxjs/toolkit'; -import { - syntheticsMonitorReducer, - SyntheticsMonitorState, -} from './monitor_summary/synthetics_montior_reducer'; -import { monitorStatusReducer, MonitorSummaryState } from './monitor_summary'; +import { monitorDetailsReducer, MonitorDetailsState } from './monitor_details'; import { uiReducer, UiState } from './ui'; import { indexStatusReducer, IndexStatusState } from './index_status'; import { syntheticsEnablementReducer, SyntheticsEnablementState } from './synthetics_enablement'; import { monitorListReducer, MonitorListState } from './monitor_list'; import { serviceLocationsReducer, ServiceLocationsState } from './service_locations'; import { monitorOverviewReducer, MonitorOverviewState } from './overview'; +import { BrowserJourneyState } from './browser_journey/models'; +import { browserJourneyReducer } from './browser_journey'; export interface SyntheticsAppState { ui: UiState; @@ -25,9 +23,9 @@ export interface SyntheticsAppState { syntheticsEnablement: SyntheticsEnablementState; monitorList: MonitorListState; serviceLocations: ServiceLocationsState; - monitorStatus: MonitorSummaryState; - syntheticsMonitor: SyntheticsMonitorState; + monitorDetails: MonitorDetailsState; overview: MonitorOverviewState; + browserJourney: BrowserJourneyState; } export const rootReducer = combineReducers({ @@ -36,7 +34,7 @@ export const rootReducer = combineReducers({ syntheticsEnablement: syntheticsEnablementReducer, monitorList: monitorListReducer, serviceLocations: serviceLocationsReducer, - monitorStatus: monitorStatusReducer, - syntheticsMonitor: syntheticsMonitorReducer, + monitorDetails: monitorDetailsReducer, overview: monitorOverviewReducer, + browserJourney: browserJourneyReducer, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/http_error.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/http_error.ts index 34c5a021c5c03..5c296eedb79f9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/http_error.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/http_error.ts @@ -18,6 +18,18 @@ export interface IHttpSerializedFetchError { } export const serializeHttpFetchError = (error: IHttpFetchError): IHttpSerializedFetchError => { + if (error.name && !error.body) { + return { + name: error.name, + body: { + error: error.toString(), + message: error.message, + statusCode: undefined, + }, + requestUrl: error?.request?.url, + }; + } + const body = error.body as { error: string; message: string; statusCode: number }; return { name: error.name, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx index da86d7d9510b6..923a0b9bb16f5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx @@ -71,7 +71,15 @@ const Application = (props: SyntheticsAppProps) => { return ( - + { + let getContextMock: jest.Mock; + let drawImageMock: jest.Mock; + let ref: ScreenshotRefImageData; + let contextMock: unknown; + + beforeEach(() => { + drawImageMock = jest.fn(); + contextMock = { + drawImage: drawImageMock, + }; + getContextMock = jest.fn().mockReturnValue(contextMock); + ref = { + stepName: 'step', + maxSteps: 3, + ref: { + screenshotRef: { + '@timestamp': '123', + monitor: { check_group: 'check-group' }, + screenshot_ref: { + blocks: [ + { + hash: '123', + top: 0, + left: 0, + width: 10, + height: 10, + }, + ], + height: 100, + width: 100, + }, + synthetics: { + package_version: 'v1', + step: { + name: 'step-name', + index: 0, + }, + type: 'step/screenshot_ref', + }, + }, + }, + }; + }); + + it('throws error when blob does not exist', async () => { + try { + // @ts-expect-error incomplete invocation for test + await composeScreenshotRef(ref, { getContext: getContextMock }, {}); + } catch (e: any) { + expect(e).toMatchInlineSnapshot( + `[Error: Error processing image. Expected image data with hash 123 is missing]` + ); + expect(getContextMock).toHaveBeenCalled(); + expect(getContextMock.mock.calls[0][0]).toBe('2d'); + expect(getContextMock.mock.calls[0][1]).toEqual({ alpha: false }); + } + }); + + it('throws error when block is pending', async () => { + try { + await composeScreenshotRef( + ref, + // @ts-expect-error incomplete invocation for test + { getContext: getContextMock }, + { '123': { status: 'pending' } } + ); + } catch (e: any) { + expect(e).toMatchInlineSnapshot( + `[Error: Error processing image. Expected image data with hash 123 is missing]` + ); + expect(getContextMock).toHaveBeenCalled(); + expect(getContextMock.mock.calls[0][0]).toBe('2d'); + expect(getContextMock.mock.calls[0][1]).toEqual({ alpha: false }); + } + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.ts new file mode 100644 index 0000000000000..ed9f4836d9b88 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/compose_screenshot_images.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 { + isScreenshotBlockDoc, + ScreenshotRefImageData, + ScreenshotBlockCache, +} from '../../../../../common/runtime_types'; + +/** + * Draws image fragments on a canvas. + * @param data Contains overall image size, fragment dimensions, and the blobs of image data to render. + * @param canvas A canvas to use for the rendering. + * @returns A promise that will resolve when the final draw operation completes. + */ +export async function composeScreenshotRef( + data: ScreenshotRefImageData, + canvas: HTMLCanvasElement, + blocks: ScreenshotBlockCache +) { + const { + ref: { screenshotRef }, + } = data; + + const ctx = canvas.getContext('2d', { alpha: false }); + + canvas.width = screenshotRef.screenshot_ref.width; + canvas.height = screenshotRef.screenshot_ref.height; + + /** + * We need to treat each operation as an async task, otherwise we will race between drawing image + * chunks and extracting the final data URL from the canvas; without this, the image could be blank or incomplete. + */ + const drawOperations: Array> = []; + + for (const { hash, top, left, width, height } of screenshotRef.screenshot_ref.blocks) { + drawOperations.push( + new Promise((resolve, reject) => { + const img = new Image(); + const blob = blocks[hash]; + if (!blob || !isScreenshotBlockDoc(blob)) { + reject(Error(`Error processing image. Expected image data with hash ${hash} is missing`)); + } else { + img.onload = () => { + ctx?.drawImage(img, left, top, width, height); + resolve(); + }; + img.src = `data:image/jpg;base64,${blob.synthetics.blob}`; + } + }) + ); + } + + // once all `draw` operations finish, caller can extract img string + return Promise.all(drawOperations); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/sort_pings.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/sort_pings.ts new file mode 100644 index 0000000000000..7922e046dfbaf --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/sort_pings.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 { get as getProp } from 'lodash'; +import { Ping } from '../../../../../common/runtime_types'; + +export function sortPings(pings: Ping[], sortField: string, sortDirection: 'asc' | 'desc') { + const toSort = [...pings]; + toSort.sort((a, b) => { + let propA = getProp(a, sortField) ?? null; + let propB = getProp(b, sortField) ?? null; + + if (propA === null || propB === null) { + return 0; + } + + if (sortField === 'timestamp') { + propA = new Date(propA); + propB = new Date(propB); + } + + if (sortField === 'monitor.status') { + propA = propA === 'up' ? -1 : 1; + propB = propB === 'up' ? -1 : 1; + } + + if (typeof propA === 'string') { + return sortDirection === 'asc' ? propA.localeCompare(propB) : propB.localeCompare(propA); + } + + return sortDirection === 'asc' ? propA - propB : propB - propA; + }); + + return toSort; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts new file mode 100644 index 0000000000000..005872224b08e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.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 moment from 'moment'; +import { i18n } from '@kbn/i18n'; + +/** + * Formats the microseconds (µ) into either milliseconds (ms) or seconds (s) based on the duration value + * @param us {number} duration value in microseconds + */ +export const formatTestDuration = (us?: number) => { + const microSecs = us ?? 0; + const secs = microSecs / (1000 * 1000); + if (secs >= 1) { + return `${secs.toFixed(1)} s`; + } + + return `${(microSecs / 1000).toFixed(0)} ms`; +}; + +export function formatTestRunAt(timestamp: string) { + const stampedMoment = moment(timestamp); + const startOfToday = moment().startOf('day'); + const startOfYesterday = moment().add(-1, 'day'); + + const dateStr = + stampedMoment > startOfToday + ? `${TODAY_LABEL}` + : stampedMoment > startOfYesterday + ? `${YESTERDAY_LABEL}` + : `${stampedMoment.format('ll')} `; + + const timeStr = stampedMoment.format('HH:mm:ss'); + + return `${dateStr} @ ${timeStr}`; +} + +const TODAY_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.today', { + defaultMessage: 'Today', +}); + +const YESTERDAY_LABEL = i18n.translate('xpack.synthetics.monitorDetails.summary.yesterday', { + defaultMessage: 'Yesterday', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/timestamp.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/timestamp.ts new file mode 100644 index 0000000000000..c9c4d022b869d --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/timestamp.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 moment from 'moment'; +import { SHORT_TIMESPAN_LOCALE, SHORT_TS_LOCALE } from '../../../../../common/constants'; + +export const parseTimestamp = (tsValue: string): moment.Moment => { + let parsed = Date.parse(tsValue); + if (isNaN(parsed)) { + parsed = parseInt(tsValue, 10); + } + return moment(parsed); +}; + +export const getShortTimeStamp = (timeStamp: moment.Moment, relative = false) => { + if (relative) { + const prevLocale: string = moment.locale() ?? 'en'; + + const shortLocale = moment.locale(SHORT_TS_LOCALE) === SHORT_TS_LOCALE; + + if (!shortLocale) { + moment.defineLocale(SHORT_TS_LOCALE, SHORT_TIMESPAN_LOCALE); + } + + let shortTimestamp; + if (typeof (timeStamp as unknown) === 'string') { + shortTimestamp = parseTimestamp(timeStamp as unknown as string).fromNow(); + } else { + shortTimestamp = timeStamp.fromNow(); + } + + // Reset it so, it doesn't impact other part of the app + moment.locale(prevLocale); + return shortTimestamp; + } else { + if (moment().diff(timeStamp, 'd') >= 1) { + return timeStamp.format('ll LTS'); + } + return timeStamp.format('LTS'); + } +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/screenshot_ref.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/screenshot_ref.mock.ts new file mode 100644 index 0000000000000..f704c3309c1f9 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/screenshot_ref.mock.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; + +export const mockRef: ScreenshotRefImageData = { + maxSteps: 1, + stepName: 'load homepage', + ref: { + screenshotRef: { + '@timestamp': '2021-06-08T19:42:30.257Z', + synthetics: { + package_version: '1.0.0-beta.2', + step: { name: 'load homepage', index: 1 }, + type: 'step/screenshot_ref', + }, + screenshot_ref: { + blocks: [ + { + top: 0, + left: 0, + width: 160, + hash: 'd518801fc523cf02727cd520f556c4113b3098c7', + height: 90, + }, + { + top: 0, + left: 160, + width: 160, + hash: 'fa90345d5d7b05b1601e9ee645e663bc358869e0', + height: 90, + }, + ], + width: 1280, + height: 720, + }, + monitor: { check_group: 'a567cc7a-c891-11eb-bdf9-3e22fb19bf97' }, + }, + }, +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index a91c95f89abc8..57ce5e39a8dbd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -8,8 +8,11 @@ import { SyntheticsAppState } from '../../../state/root_reducer'; import { ConfigKey, + DataStream, DEFAULT_THROTTLING, LocationStatus, + ScheduleUnit, + SourceType, } from '../../../../../../common/runtime_types'; /** @@ -93,15 +96,255 @@ export const mockState: SyntheticsAppState = { loading: false, }, syntheticsEnablement: { loading: false, error: null, enablement: null }, - monitorStatus: { - data: null, - loading: false, - error: null, - selectedLocationId: null, - }, - syntheticsMonitor: { - data: null, + monitorDetails: getMonitorDetailsMockSlice(), + browserJourney: getBrowserJourneyMockSlice(), +}; + +function getBrowserJourneyMockSlice() { + return { + blocks: { + '4bae236101175ae7746cb922f4c511083af4fbcd': { + id: '4bae236101175ae7746cb922f4c511083af4fbcd', + synthetics: { + blob: '/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABaAKADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//9k=', + blob_mime: 'image/jpeg', + }, + }, + ec95c047e2e05a27598451fdaa7f24db973eb933: { + id: 'ec95c047e2e05a27598451fdaa7f24db973eb933', + synthetics: { + blob: '/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABaAKADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAMI/8QAGhABAAMBAQEAAAAAAAAAAAAAAAECAwQRIf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDNfT0bdXTr0dWum3RtedNNdLTa17TPs2mZ+zMz99TAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/2Q==', + blob_mime: 'image/jpeg', + }, + }, + }, + cacheSize: 0, + hitCount: [ + { hash: '4bae236101175ae7746cb922f4c511083af4fbcd', hitTime: 1658682270849 }, + { hash: 'ec95c047e2e05a27598451fdaa7f24db973eb933', hitTime: 1658682270849 }, + ], + }; +} + +function getMonitorDetailsMockSlice() { + return { + pings: [ + { + summary: { up: 1, down: 0 }, + agent: { + name: 'cron-b010e1cc9518984e-27644714-4pd4h', + id: 'f8721d90-5aec-4815-a6f1-f4d4a6fb7482', + type: 'heartbeat', + ephemeral_id: 'd6a60494-5e52-418f-922b-8e90f0b4013c', + version: '8.3.0', + }, + synthetics: { + journey: { name: 'inline', id: 'inline', tags: null }, + type: 'heartbeat/summary', + }, + monitor: { + duration: { us: 269722 }, + origin: SourceType.UI, + name: 'One pixel monitor', + check_group: '051aba1c-0b74-11ed-9f0e-ba4e6fa109d5', + id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + timespan: { lt: '2022-07-24T17:24:06.094Z', gte: '2022-07-24T17:14:06.094Z' }, + type: DataStream.BROWSER, + status: 'up', + }, + url: { + scheme: 'data', + domain: '', + full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + }, + observer: { + geo: { + continent_name: 'North America', + city_name: 'Iowa', + country_iso_code: 'US', + name: 'North America - US Central', + location: '41.8780, 93.0977', + }, + hostname: 'cron-b010e1cc9518984e-27644714-4pd4h', + ip: ['10.1.11.162'], + mac: ['ba:4e:6f:a1:09:d5'], + }, + '@timestamp': '2022-07-24T17:14:05.079Z', + ecs: { version: '8.0.0' }, + config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, + 'event.type': 'journey/end', + event: { + agent_id_status: 'auth_metadata_missing', + ingested: '2022-07-24T17:14:07Z', + type: 'heartbeat/summary', + dataset: 'browser', + }, + timestamp: '2022-07-24T17:14:05.079Z', + docId: 'AkYzMYIBqL6WCtugsFck', + }, + { + summary: { up: 1, down: 0 }, + agent: { + name: 'cron-b010e1cc9518984e-27644704-zs98t', + id: 'a9620214-591d-48e7-9e5d-10b7a9fb1a03', + type: 'heartbeat', + ephemeral_id: 'c5110885-81b4-4e9a-8747-690d19fbd225', + version: '8.3.0', + }, + synthetics: { + journey: { name: 'inline', id: 'inline', tags: null }, + type: 'heartbeat/summary', + }, + monitor: { + duration: { us: 227326 }, + origin: SourceType.UI, + name: 'One pixel monitor', + id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + check_group: '9eb87e53-0b72-11ed-b34f-aa618b4334ae', + timespan: { lt: '2022-07-24T17:14:05.020Z', gte: '2022-07-24T17:04:05.020Z' }, + type: DataStream.BROWSER, + status: 'up', + }, + url: { + scheme: 'data', + domain: '', + full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + }, + observer: { + geo: { + continent_name: 'North America', + city_name: 'Iowa', + country_iso_code: 'US', + name: 'North America - US Central', + location: '41.8780, 93.0977', + }, + hostname: 'cron-b010e1cc9518984e-27644704-zs98t', + ip: ['10.1.9.133'], + mac: ['aa:61:8b:43:34:ae'], + }, + '@timestamp': '2022-07-24T17:04:03.769Z', + ecs: { version: '8.0.0' }, + config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, + 'event.type': 'journey/end', + event: { + agent_id_status: 'auth_metadata_missing', + ingested: '2022-07-24T17:04:06Z', + type: 'heartbeat/summary', + dataset: 'browser', + }, + timestamp: '2022-07-24T17:04:03.769Z', + docId: 'mkYqMYIBqL6WCtughFUq', + }, + { + summary: { up: 1, down: 0 }, + agent: { + name: 'job-b010e1cc9518984e-dkw5k', + id: 'e3a4e3a8-bdd1-44fe-86f5-e451b80f80c5', + type: 'heartbeat', + ephemeral_id: 'f41a13ab-a85d-4614-89c0-8dbad6a32868', + version: '8.3.0', + }, + synthetics: { + journey: { name: 'inline', id: 'inline', tags: null }, + type: 'heartbeat/summary', + }, + monitor: { + duration: { us: 207700 }, + origin: SourceType.UI, + name: 'One pixel monitor', + timespan: { lt: '2022-07-24T17:11:49.702Z', gte: '2022-07-24T17:01:49.702Z' }, + check_group: '4e00ac5a-0b72-11ed-a97e-5203642c687d', + id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + type: DataStream.BROWSER, + status: 'up', + }, + url: { + scheme: 'data', + domain: '', + full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + }, + observer: { + geo: { + continent_name: 'North America', + city_name: 'Iowa', + country_iso_code: 'US', + name: 'North America - US Central', + location: '41.8780, 93.0977', + }, + hostname: 'job-b010e1cc9518984e-dkw5k', + ip: ['10.1.9.132'], + mac: ['52:03:64:2c:68:7d'], + }, + '@timestamp': '2022-07-24T17:01:48.326Z', + ecs: { version: '8.0.0' }, + config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, + 'event.type': 'journey/end', + event: { + agent_id_status: 'auth_metadata_missing', + ingested: '2022-07-24T17:01:50Z', + type: 'heartbeat/summary', + dataset: 'browser', + }, + timestamp: '2022-07-24T17:01:48.326Z', + docId: 'kUYoMYIBqL6WCtugc1We', + }, + ], loading: false, + syntheticsMonitor: { + id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + type: DataStream.BROWSER, + enabled: true, + schedule: { unit: ScheduleUnit.MINUTES, number: '10' }, + 'service.name': '', + tags: [], + timeout: null, + name: 'One pixel monitor', + locations: [{ isServiceManaged: true, id: 'us_central' }], + namespace: 'default', + origin: SourceType.UI, + journey_id: '', + project_id: '', + playwright_options: '', + __ui: { + script_source: { is_generated_script: false, file_name: '' }, + is_zip_url_tls_enabled: false, + is_tls_enabled: false, + }, + params: '', + 'url.port': null, + 'source.inline.script': + "step('Goto one pixel image', async () => {\\n await page.goto('data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==');\\n});", + 'source.project.content': '', + 'source.zip_url.url': '', + 'source.zip_url.username': '', + 'source.zip_url.password': '', + 'source.zip_url.folder': '', + 'source.zip_url.proxy_url': '', + urls: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + screenshots: 'on', + synthetics_args: [], + 'filter_journeys.match': '', + 'filter_journeys.tags': [], + ignore_https_errors: false, + 'throttling.is_enabled': true, + 'throttling.download_speed': '5', + 'throttling.upload_speed': '3', + 'throttling.latency': '20', + 'throttling.config': '5d/3u/20l', + 'ssl.certificate_authorities': '', + 'ssl.certificate': '', + 'ssl.key': '', + 'ssl.key_passphrase': '', + 'ssl.verification_mode': 'full', + 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + revision: 1, + updated_at: '2022-07-24T17:15:46.342Z', + }, + syntheticsMonitorLoading: false, error: null, - }, -}; + selectedLocationId: 'us_central', + }; +} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/use_composite_image.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/use_composite_image.mock.ts similarity index 76% rename from x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/use_composite_image.mock.ts rename to x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/use_composite_image.mock.ts index 64bc0776b8207..1e2aafff28dca 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/use_composite_image.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/use_composite_image.mock.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { ScreenshotRefImageData } from '../../../../common/runtime_types/ping/synthetics'; -import * as composeScreenshotImages from '../../hooks/use_composite_image'; +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; +import * as composeScreenshotImages from '../../../hooks/use_composite_image'; jest .spyOn(composeScreenshotImages, 'useCompositeImage') diff --git a/x-pack/plugins/synthetics/public/hooks/use_breakpoints.test.ts b/x-pack/plugins/synthetics/public/hooks/use_breakpoints.test.ts deleted file mode 100644 index d417d98dcb76d..0000000000000 --- a/x-pack/plugins/synthetics/public/hooks/use_breakpoints.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { BREAKPOINTS } from '@elastic/eui'; -import { renderHook } from '@testing-library/react-hooks'; -import { useBreakpoints } from './use_breakpoints'; - -describe('use_breakpoints', () => { - describe('useBreakpoints', () => { - const width = global.innerWidth; - - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.runOnlyPendingTimers(); - jest.useRealTimers(); - }); - - afterAll(() => { - (global as { innerWidth: number }).innerWidth = width; - }); - - it('should only return up => false and down => true for "xs" when width is less than BREAKPOINTS.xs', () => { - (global as { innerWidth: number }).innerWidth = BREAKPOINTS.xs - 1; - const { result } = renderHook(() => useBreakpoints()); - - expect(result.current.up('xs')).toBeFalsy(); - expect(result.current.down('xs')).toBeTruthy(); - }); - - it('should only return up => true and down => false for "xs" when width is above or equal BREAKPOINTS.xs', () => { - (global as { innerWidth: number }).innerWidth = BREAKPOINTS.xs; - const { result } = renderHook(() => useBreakpoints()); - - expect(result.current.up('xs')).toBeTruthy(); - expect(result.current.down('xs')).toBeFalsy(); - }); - - it('should return down => true for "m" when width equals BREAKPOINTS.l', () => { - (global as { innerWidth: number }).innerWidth = BREAKPOINTS.l; - const { result } = renderHook(() => useBreakpoints()); - - expect(result.current.up('m')).toBeTruthy(); - expect(result.current.down('m')).toBeFalsy(); - }); - - it('should return `between` => true for "m" and "xl" when width equals BREAKPOINTS.l', () => { - (global as { innerWidth: number }).innerWidth = BREAKPOINTS.l; - const { result } = renderHook(() => useBreakpoints()); - - expect(result.current.between('m', 'xl')).toBeTruthy(); - }); - - it('should return `between` => true for "s" and "m" when width equals BREAKPOINTS.s', () => { - (global as { innerWidth: number }).innerWidth = BREAKPOINTS.s; - const { result } = renderHook(() => useBreakpoints()); - - expect(result.current.between('s', 'm')).toBeTruthy(); - }); - - it('should return up => true for all when size is > xxxl+', () => { - (global as { innerWidth: number }).innerWidth = 3000; - const { result } = renderHook(() => useBreakpoints()); - - expect(result.current.up('xs')).toBeTruthy(); - expect(result.current.up('s')).toBeTruthy(); - expect(result.current.up('m')).toBeTruthy(); - expect(result.current.up('l')).toBeTruthy(); - expect(result.current.up('xl')).toBeTruthy(); - expect(result.current.up('xxl')).toBeTruthy(); - expect(result.current.up('xxxl')).toBeTruthy(); - }); - - it('should determine `isIpad (Portrait)', () => { - (global as { innerWidth: number }).innerWidth = 768; - const { result } = renderHook(() => useBreakpoints()); - - const isIpad = result.current.up('m') && result.current.down('l'); - expect(isIpad).toEqual(true); - }); - - it('should determine `isMobile (Portrait)`', () => { - (global as { innerWidth: number }).innerWidth = 480; - const { result } = renderHook(() => useBreakpoints()); - - const isMobile = result.current.up('xs') && result.current.down('s'); - expect(isMobile).toEqual(true); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/hooks/use_breakpoints.ts b/x-pack/plugins/synthetics/public/hooks/use_breakpoints.ts deleted file mode 100644 index 9398a5fcd15fe..0000000000000 --- a/x-pack/plugins/synthetics/public/hooks/use_breakpoints.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useState } from 'react'; -import useWindowSize from 'react-use/lib/useWindowSize'; -import useDebounce from 'react-use/lib/useDebounce'; - -import { BREAKPOINTS, EuiBreakpointSize } from '@elastic/eui'; - -// Custom breakpoints -const BREAKPOINT_XL = 1599; // Overriding the theme's default 'xl' breakpoint -const BREAKPOINT_XXL = 1599; -const BREAKPOINT_XXXL = 2000; - -export type BreakpointKey = EuiBreakpointSize | 'xxl' | 'xxxl'; - -type BreakpointPredicate = (breakpointKey: BreakpointKey) => boolean; -type BreakpointRangePredicate = (from: BreakpointKey, to: BreakpointKey) => boolean; - -/** - * Returns the predicates functions used to determine whether the current device's width is above or below the asked - * breakpoint. (Implementation inspired by React Material UI). - * - * @example - * const { breakpoints } = useBreakpoints(); - * const isMobile = breakpoint.down('m'); - * - * @example - * const { breakpoints } = useBreakpoints(); - * const isTablet = breakpoint.between('m', 'l'); - * - * @param debounce {number} Debounce interval for optimization - * - * @returns { {up: BreakpointPredicate, down: BreakpointPredicate, between: BreakpointRangePredicate} } - * Returns object containing predicates which determine whether the current device's width lies above, below or - * in-between the given breakpoint(s) - * { - * up => Returns `true` if the current width is equal or above (inclusive) the given breakpoint size, - * or `false` otherwise. - * down => Returns `true` if the current width is below (exclusive) the given breakpoint size, or `false` otherwise. - * between => Returns `true` if the current width is equal or above (inclusive) the corresponding size of - * `fromBreakpointKey` AND is below (exclusive) the corresponding width of `toBreakpointKey`. - * Returns `false` otherwise. - * } - */ -export function useBreakpoints(debounce = 50) { - const { width } = useWindowSize(); - const [debouncedWidth, setDebouncedWidth] = useState(width); - - const up = useCallback( - (breakpointKey: BreakpointKey) => isUp(debouncedWidth, breakpointKey), - [debouncedWidth] - ); - const down = useCallback( - (breakpointKey: BreakpointKey) => isDown(debouncedWidth, breakpointKey), - [debouncedWidth] - ); - - const between = useCallback( - (fromBreakpointKey: BreakpointKey, toBreakpointKey: BreakpointKey) => - isBetween(debouncedWidth, fromBreakpointKey, toBreakpointKey), - [debouncedWidth] - ); - - useDebounce( - () => { - setDebouncedWidth(width); - }, - debounce, - [width] - ); - - return { up, down, between, debouncedWidth }; -} - -/** - * Returns the corresponding device width against the provided breakpoint key, either the overridden value or the - * default value from theme. - * @param key {BreakpointKey} string key representing the device breakpoint e.g. 'xs', 's', 'xxxl' - */ -function getSizeForBreakpointKey(key: BreakpointKey): number { - switch (key) { - case 'xxxl': - return BREAKPOINT_XXXL; - case 'xxl': - return BREAKPOINT_XXL; - case 'xl': - return BREAKPOINT_XL; - case 'l': - return BREAKPOINTS.l; - case 'm': - return BREAKPOINTS.m; - case 's': - return BREAKPOINTS.s; - } - - return BREAKPOINTS.xs; -} - -function isUp(size: number, breakpointKey: BreakpointKey) { - return size >= getSizeForBreakpointKey(breakpointKey); -} - -function isDown(size: number, breakpointKey: BreakpointKey) { - return size < getSizeForBreakpointKey(breakpointKey); -} - -function isBetween(size: number, fromBreakpointKey: BreakpointKey, toBreakpointKey: BreakpointKey) { - return isUp(size, fromBreakpointKey) && isDown(size, toBreakpointKey); -} diff --git a/x-pack/plugins/synthetics/public/kibana_services.ts b/x-pack/plugins/synthetics/public/kibana_services.ts new file mode 100644 index 0000000000000..eb413b0260fb1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/kibana_services.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 { CoreStart } from '@kbn/core/public'; + +let coreStart: CoreStart; +export function setStartServices(core: CoreStart) { + coreStart = core; +} + +export const getDocLinks = () => coreStart.docLinks; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx index 748918026e057..dc8c5918ec31a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx @@ -104,7 +104,15 @@ const Application = (props: UptimeAppProps) => { return ( - + { - const down = jest.fn().mockReturnValue(false); - return { - useBreakpoints: () => ({ down }), - }; -}); describe('UptimePageTemplateComponent', () => { describe('styling', () => { @@ -34,7 +26,7 @@ describe('UptimePageTemplateComponent', () => { }); it('applies the header centering on mobile', () => { - (useBreakpoints().down as jest.Mock).mockReturnValue(true); + window.innerWidth = 600; const { container } = render(); expect(container.firstChild).toMatchSnapshot(); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx index 9822ec130a8bf..df7735aef2695 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useMemo } from 'react'; import styled from 'styled-components'; -import { EuiPageHeaderProps, EuiPageTemplateProps } from '@elastic/eui'; +import { EuiPageHeaderProps, EuiPageTemplateProps, useIsWithinMaxBreakpoint } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; import { CERTIFICATES_ROUTE, OVERVIEW_ROUTE } from '../../../common/constants'; @@ -16,7 +16,6 @@ import { useNoDataConfig } from './use_no_data_config'; import { EmptyStateLoading } from '../components/overview/empty_state/empty_state_loading'; import { EmptyStateError } from '../components/overview/empty_state/empty_state_error'; import { useHasData } from '../components/overview/empty_state/use_has_data'; -import { useBreakpoints } from '../../hooks/use_breakpoints'; interface Props { path: string; @@ -38,8 +37,7 @@ export const UptimePageTemplateComponent: React.FC const { services: { observability }, } = useKibana(); - const { down } = useBreakpoints(); - const isMobile = down('s'); + const isMobile = useIsWithinMaxBreakpoint('s'); const PageTemplateComponent = observability.navigation.PageTemplate; const StyledPageTemplateComponent = useMemo(() => { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/charts/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/charts/__snapshots__/donut_chart.test.tsx.snap index 9e7b46568882b..b4486e65c5396 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/charts/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/charts/__snapshots__/donut_chart.test.tsx.snap @@ -403,6 +403,8 @@ exports[`DonutChart component passes correct props without errors for valid prop "metric": Object { "background": "#FFFFFF", "barBackground": "#EDF0F5", + "border": "#EDF0F5", + "minHeight": 64, "nonFiniteText": "N/A", "text": Object { "darkColor": "#343741", diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/normalizers.ts index 0746f2cdae279..a8b59b16a3460 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/normalizers.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/normalizers.ts @@ -107,7 +107,6 @@ export const browserNormalizers: BrowserNormalizerMap = { ConfigKey.JOURNEY_FILTERS_TAGS ), [ConfigKey.IGNORE_HTTPS_ERRORS]: getBrowserNormalizer(ConfigKey.IGNORE_HTTPS_ERRORS), - [ConfigKey.JOURNEY_ID]: getBrowserNormalizer(ConfigKey.JOURNEY_ID), [ConfigKey.PROJECT_ID]: getBrowserNormalizer(ConfigKey.PROJECT_ID), [ConfigKey.PLAYWRIGHT_OPTIONS]: getBrowserNormalizer(ConfigKey.PLAYWRIGHT_OPTIONS), [ConfigKey.CUSTOM_HEARTBEAT_ID]: getBrowserNormalizer(ConfigKey.CUSTOM_HEARTBEAT_ID), diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts index bdea46781dfd9..14fab3caeb132 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts @@ -92,4 +92,5 @@ export const commonNormalizers: CommonNormalizerMap = { [ConfigKey.REVISION]: getCommonNormalizer(ConfigKey.REVISION), [ConfigKey.MONITOR_SOURCE_TYPE]: getCommonNormalizer(ConfigKey.MONITOR_SOURCE_TYPE), [ConfigKey.FORM_MONITOR_TYPE]: getCommonNormalizer(ConfigKey.FORM_MONITOR_TYPE), + [ConfigKey.JOURNEY_ID]: getCommonNormalizer(ConfigKey.JOURNEY_ID), }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx index 6a429d98756af..3e2225af63a1a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx @@ -13,8 +13,8 @@ import { render } from '../../../../../lib/helper/rtl_helpers'; import * as observabilityPublic from '@kbn/observability-plugin/public'; import { getShortTimeStamp } from '../../../../overview/monitor_list/columns/monitor_status_column'; import moment from 'moment'; -import '../../../../../lib/__mocks__/use_composite_image.mock'; -import { mockRef } from '../../../../../lib/__mocks__/screenshot_ref.mock'; +import '../../../../../lib/__mocks__/legacy_use_composite_image.mock'; +import { mockRef } from '../../../../../lib/__mocks__/legacy_screenshot_ref.mock'; jest.mock('@kbn/observability-plugin/public'); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx index d52c9ab0c896a..18df82473468b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx @@ -11,7 +11,7 @@ import { render } from '../../../../../lib/helper/rtl_helpers'; import { StepImageCaption, StepImageCaptionProps } from './step_image_caption'; import { getShortTimeStamp } from '../../../../overview/monitor_list/columns/monitor_status_column'; import moment from 'moment'; -import { mockRef } from '../../../../../lib/__mocks__/screenshot_ref.mock'; +import { mockRef } from '../../../../../lib/__mocks__/legacy_screenshot_ref.mock'; describe('StepImageCaption', () => { let defaultProps: StepImageCaptionProps; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx index 73996c4e3a1b7..b37daa81e2349 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx @@ -6,11 +6,17 @@ */ import React, { MouseEvent, useEffect } from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiText, + useEuiTheme, + useIsWithinMaxBreakpoint, +} from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { ScreenshotRefImageData } from '../../../../../../../common/runtime_types'; -import { useBreakpoints } from '../../../../../../hooks/use_breakpoints'; import { nextAriaLabel, prevAriaLabel } from './translations'; @@ -38,7 +44,7 @@ export const StepImageCaption: React.FC = ({ onVisible, }) => { const { euiTheme } = useEuiTheme(); - const breakpoints = useBreakpoints(); + const isSmall = useIsWithinMaxBreakpoint('m'); useEffect(() => { onVisible(true); @@ -48,8 +54,6 @@ export const StepImageCaption: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const isSmall = breakpoints.down('m'); - return ( { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx index f0e69395f6075..0cae34a05e35b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useContext, useState, useEffect } from 'react'; +import React, { useCallback, useContext, useState, useEffect, useRef } from 'react'; import { useParams, Redirect } from 'react-router-dom'; import { EuiFlexGroup, @@ -38,6 +38,11 @@ import { monitorManagementListSelector } from '../../../state/selectors'; import { kibanaService } from '../../../state/kibana_service'; +import { + PRIVATE_AVAILABLE_LABEL, + TEST_SCHEDULED_LABEL, +} from '../../overview/monitor_list/translations'; + export interface ActionBarProps { monitor: SyntheticsMonitor; isValid: boolean; @@ -63,9 +68,11 @@ export const ActionBar = ({ const [isSaving, setIsSaving] = useState(false); const [isSuccessful, setIsSuccessful] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(undefined); + const mouseMoveTimeoutIds = useRef<[number, number]>([0, 0]); const isReadOnly = monitor[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; const hasServiceManagedLocation = monitor.locations?.some((loc) => loc.isServiceManaged); + const isOnlyPrivateLocations = !locations.some((loc) => loc.isServiceManaged); const { data, status } = useFetcher(() => { if (!isSaving || !isValid) { @@ -139,11 +146,14 @@ export const ActionBar = ({ {!isValid && hasBeenSubmitted && VALIDATION_ERROR_LABEL} + {onTestNow && ( {/* Popover is used instead of EuiTooltip until the resolution of https://github.com/elastic/eui/issues/5604 */} onTestNow()} - onMouseEnter={() => { - setIsPopoverOpen(true); + onMouseOver={() => { + // We need this custom logic to display a popover even when button is disabled. + clearTimeout(mouseMoveTimeoutIds.current[1]); + if (mouseMoveTimeoutIds.current[0] === 0) { + mouseMoveTimeoutIds.current[0] = setTimeout(() => { + clearTimeout(mouseMoveTimeoutIds.current[1]); + setIsPopoverOpen(true); + }, 250) as unknown as number; + } }} - onMouseLeave={() => { - setIsPopoverOpen(false); + onMouseOut={() => { + // We need this custom logic to display a popover even when button is disabled. + clearTimeout(mouseMoveTimeoutIds.current[1]); + mouseMoveTimeoutIds.current[1] = setTimeout(() => { + clearTimeout(mouseMoveTimeoutIds.current[0]); + setIsPopoverOpen(false); + mouseMoveTimeoutIds.current = [0, 0]; + }, 100) as unknown as number; }} > {testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL} @@ -167,7 +190,13 @@ export const ActionBar = ({ isOpen={isPopoverOpen} > -

{TEST_NOW_DESCRIPTION}

+

+ {isTestRunInProgress + ? TEST_SCHEDULED_LABEL + : isOnlyPrivateLocations || (isValid && !hasServiceManagedLocation) + ? PRIVATE_AVAILABLE_LABEL + : TEST_NOW_DESCRIPTION} +

diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts index 916ca8c00b972..098154f4e20ce 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts @@ -22,6 +22,10 @@ export function useRunOnceErrors({ }) { const [locationErrors, setLocationErrors] = useState([]); const [runOnceServiceError, setRunOnceServiceError] = useState(null); + const publicLocations = useMemo( + () => (locations ?? []).filter((loc) => loc.isServiceManaged), + [locations] + ); useEffect(() => { setLocationErrors([]); @@ -43,16 +47,16 @@ export function useRunOnceErrors({ }, [serviceError]); const locationsById: Record = useMemo( - () => (locations as Locations).reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {}), - [locations] + () => (publicLocations as Locations).reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {}), + [publicLocations] ); const expectPings = - locations.length - (locationErrors ?? []).filter(({ locationId }) => !!locationId).length; + publicLocations.length - (locationErrors ?? []).filter(({ locationId }) => !!locationId).length; const hasBlockingError = !!runOnceServiceError || - (locationErrors?.length && locationErrors?.length === locations.length); + (locationErrors?.length && locationErrors?.length === publicLocations.length); const errorMessages = useMemo(() => { if (hasBlockingError) { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.tsx index 074eaea4177d0..c603035130ca3 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.tsx @@ -12,6 +12,7 @@ import { EuiPanel, EuiSpacer, EuiText, + useIsWithinMinBreakpoint, } from '@elastic/eui'; import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; import { i18n } from '@kbn/i18n'; @@ -30,7 +31,6 @@ import { BrowserFields, } from '../../../../../common/runtime_types'; import { UptimeSettingsContext } from '../../../contexts'; -import { useBreakpoints } from '../../../../hooks/use_breakpoints'; import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management'; import * as labels from '../../overview/monitor_list/translations'; import { Actions } from './actions'; @@ -70,7 +70,7 @@ export const MonitorManagementList = ({ errorSummaries, }: Props) => { const { basePath } = useContext(UptimeSettingsContext); - const isXl = useBreakpoints().up('xl'); + const isXl = useIsWithinMinBreakpoint('xxl'); const { total } = list as MonitorManagementListState['list']; const monitors: EncryptedSyntheticsMonitorWithId[] = useMemo( diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx index 288cd7232a3d5..f8ed9cb99fb7c 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx @@ -9,9 +9,11 @@ import React, { useEffect, useState } from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { isValidKuery } from '../../query_bar/query_bar'; import * as labels from '../translations'; import { useUptimeDataView } from '../../../../hooks'; +import { ClientPluginsStart } from '../../../../../plugin'; interface Props { query: string; @@ -20,6 +22,19 @@ interface Props { export const AlertQueryBar = ({ query = '', onChange }: Props) => { const dataView = useUptimeDataView(); + const { services } = useKibana(); + + const { + appName, + notifications, + http, + docLinks, + uiSettings, + data, + unifiedSearch, + storage, + usageCollection, + } = services; const [inputVal, setInputVal] = useState(query); @@ -53,6 +68,17 @@ export const AlertQueryBar = ({ query = '', onChange }: Props) => { placeholder={i18n.translate('xpack.synthetics.alerts.searchPlaceholder.kql', { defaultMessage: 'Filter using kql syntax', })} + appName={appName} + deps={{ + unifiedSearch, + data, + storage, + notifications, + http, + docLinks, + uiSettings, + usageCollection, + }} /> ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx index 425d881e0141e..17e8047ac64c7 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx @@ -6,12 +6,12 @@ */ import { EuiButtonIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Ping } from '../../../../../../common/runtime_types'; import { testNowMonitorAction } from '../../../../state/actions'; import { testNowRunSelector, TestRunStats } from '../../../../state/reducers/test_now_runs'; +import * as labels from '../translations'; export const TestNowColumn = ({ monitorId, @@ -28,7 +28,7 @@ export const TestNowColumn = ({ if (selectedMonitor.monitor.fleet_managed) { return ( - + <>-- ); @@ -36,7 +36,7 @@ export const TestNowColumn = ({ if (!configId) { return ( - + <>-- ); @@ -54,45 +54,13 @@ export const TestNowColumn = ({ } return ( - + testNowClick()} isDisabled={!isTestNowCompleted} - aria-label={TEST_NOW_ARIA_LABEL} + aria-label={labels.TEST_NOW_ARIA_LABEL} /> ); }; - -export const TEST_NOW_ARIA_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.AriaLabel', - { - defaultMessage: 'CLick to run test now', - } -); - -export const TEST_NOW_AVAILABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.available', - { - defaultMessage: 'Test now is only available for monitors added via Monitor Management.', - } -); - -export const PRIVATE_AVAILABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.available.private', - { - defaultMessage: `You can't currently test monitors running on private locations on demand.`, - } -); - -export const TEST_NOW_LABEL = i18n.translate('xpack.synthetics.monitorList.testNow.label', { - defaultMessage: 'Test now', -}); - -export const TEST_SCHEDULED_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.scheduled', - { - defaultMessage: 'Test is already scheduled', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx index a57bbf9d71bd8..4ca114264feca 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx @@ -5,7 +5,6 @@ * 2.0. */ import React, { useState } from 'react'; -import useWindowSize from 'react-use/lib/useWindowSize'; import useDebounce from 'react-use/lib/useDebounce'; import { EuiButtonIcon, @@ -15,7 +14,7 @@ import { EuiLink, EuiPanel, EuiSpacer, - getBreakpoint, + useCurrentEuiBreakpoint, } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { X509Expiry } from '../../../../../common/runtime_types'; @@ -61,15 +60,17 @@ export const MonitorListComponent: ({ setPageSize, }) => { const [expandedDrawerIds, updateExpandedDrawerIds] = useState([]); - const { width } = useWindowSize(); + const currentBreakpoint = useCurrentEuiBreakpoint(); const [hideExtraColumns, setHideExtraColumns] = useState(false); useDebounce( () => { - setHideExtraColumns(['m', 'l'].includes(getBreakpoint(width) ?? '')); + if (currentBreakpoint) { + setHideExtraColumns(['m', 'l'].includes(currentBreakpoint)); + } }, 50, - [width] + [currentBreakpoint] ); const items = list.summaries ?? []; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts index 402846b16c875..2f3bac51ce887 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts @@ -83,3 +83,35 @@ export const STATUS_ALERT_COLUMN = i18n.translate( export const TEST_NOW_COLUMN = i18n.translate('xpack.synthetics.monitorList.testNow.label', { defaultMessage: 'Test now', }); + +export const TEST_NOW_AVAILABLE_LABEL = i18n.translate( + 'xpack.synthetics.monitorList.testNow.available', + { + defaultMessage: 'Test now is only available for monitors added via Monitor Management.', + } +); + +export const TEST_SCHEDULED_LABEL = i18n.translate( + 'xpack.synthetics.monitorList.testNow.scheduled', + { + defaultMessage: 'Test is already scheduled', + } +); + +export const PRIVATE_AVAILABLE_LABEL = i18n.translate( + 'xpack.synthetics.monitorList.testNow.available.private', + { + defaultMessage: `You can't currently test monitors running on private locations on demand.`, + } +); + +export const TEST_NOW_ARIA_LABEL = i18n.translate( + 'xpack.synthetics.monitorList.testNow.AriaLabel', + { + defaultMessage: 'Click to run test now', + } +); + +export const TEST_NOW_LABEL = i18n.translate('xpack.synthetics.monitorList.testNow.label', { + defaultMessage: 'Test now', +}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/query_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/query_bar.tsx index 3784bc58b76b2..5ca63c2a758e6 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/query_bar.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/query_bar.tsx @@ -9,9 +9,11 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexItem } from '@elastic/eui'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SyntaxType, useQueryBar } from './use_query_bar'; import { KQL_PLACE_HOLDER, SIMPLE_SEARCH_PLACEHOLDER } from './translations'; import { useGetUrlParams, useUptimeDataView } from '../../../hooks'; +import { ClientPluginsStart } from '../../../../plugin'; const SYNTAX_STORAGE = 'uptime:queryBarSyntax'; @@ -32,7 +34,19 @@ export const isValidKuery = (query: string) => { export const QueryBar = () => { const { search: urlValue } = useGetUrlParams(); + const { services } = useKibana(); + const { + appName, + notifications, + http, + docLinks, + uiSettings, + data, + unifiedSearch, + storage, + usageCollection, + } = services; const { query, setQuery, submitImmediately } = useQueryBar(); const dataView = useUptimeDataView(); @@ -78,6 +92,17 @@ export const QueryBar = () => { query.language === SyntaxType.kuery ? KQL_PLACE_HOLDER : SIMPLE_SEARCH_PLACEHOLDER } isInvalid={isInValid()} + appName={appName} + deps={{ + unifiedSearch, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + usageCollection, + }} /> ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/step_screenshot_display.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/step_screenshot_display.test.tsx index 83e1a4e938650..cbfd48e31788b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/step_screenshot_display.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/step_screenshot_display.test.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { StepScreenshotDisplay } from './step_screenshot_display'; import { render } from '../../lib/helper/rtl_helpers'; import * as observabilityPublic from '@kbn/observability-plugin/public'; -import '../../lib/__mocks__/use_composite_image.mock'; -import { mockRef } from '../../lib/__mocks__/screenshot_ref.mock'; +import '../../lib/__mocks__/legacy_use_composite_image.mock'; +import { mockRef } from '../../lib/__mocks__/legacy_screenshot_ref.mock'; jest.mock('@kbn/observability-plugin/public'); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.test.tsx index 50fc366f50dbe..d26342aca54c5 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.test.tsx @@ -7,8 +7,7 @@ import * as redux from 'react-redux'; import { renderHook } from '@testing-library/react-hooks'; -import { ScreenshotRefImageData } from '../../../common/runtime_types'; -import { ScreenshotBlockCache } from '../state/reducers/synthetics'; +import { ScreenshotRefImageData, ScreenshotBlockCache } from '../../../common/runtime_types'; import { shouldCompose, useCompositeImage } from './use_composite_image'; import * as compose from '../lib/helper/compose_screenshot_images'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.ts b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.ts index ca783bdd290c4..9978a6c920d77 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_composite_image.ts @@ -8,13 +8,13 @@ import { useDispatch, useSelector } from 'react-redux'; import React from 'react'; import { composeScreenshotRef } from '../lib/helper/compose_screenshot_images'; -import { ScreenshotRefImageData } from '../../../common/runtime_types/ping/synthetics'; import { - fetchBlocksAction, - isPendingBlock, + ScreenshotRefImageData, ScreenshotBlockCache, StoreScreenshotBlock, -} from '../state/reducers/synthetics'; + isPendingBlock, +} from '../../../common/runtime_types'; +import { fetchBlocksAction } from '../state/reducers/synthetics'; import { syntheticsSelector } from '../state/selectors'; function allBlocksLoaded(blocks: { [key: string]: StoreScreenshotBlock }, hashes: string[]) { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/screenshot_ref.mock.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_screenshot_ref.mock.ts similarity index 100% rename from x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/screenshot_ref.mock.ts rename to x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_screenshot_ref.mock.ts diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_use_composite_image.mock.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_use_composite_image.mock.ts new file mode 100644 index 0000000000000..01a2e75b9a5ff --- /dev/null +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/legacy_use_composite_image.mock.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshotRefImageData } from '../../../../common/runtime_types'; +import * as composeScreenshotImages from '../../hooks/use_composite_image'; + +jest + .spyOn(composeScreenshotImages, 'useCompositeImage') + .mockImplementation( + ( + _imgRef: ScreenshotRefImageData, + callback: React.Dispatch, + url?: string + ) => { + if (!url) { + callback('img src'); + } + } + ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/compose_screenshot_images.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/compose_screenshot_images.ts index 86c7a001b95ab..ea9593ee1b0b7 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/compose_screenshot_images.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/compose_screenshot_images.ts @@ -8,8 +8,8 @@ import { isScreenshotBlockDoc, ScreenshotRefImageData, -} from '../../../../common/runtime_types/ping/synthetics'; -import { ScreenshotBlockCache } from '../../state/reducers/synthetics'; + ScreenshotBlockCache, +} from '../../../../common/runtime_types'; /** * Draws image fragments on a canvas. diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.test.tsx index b3aa4714fa664..30ea0e361580a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.test.tsx @@ -9,7 +9,8 @@ import React from 'react'; import { OverviewPageComponent } from './overview'; import { render } from '../lib/helper/rtl_helpers'; -describe('MonitorPage', () => { +// FLAKY: https://github.com/elastic/kibana/issues/131346 +describe.skip('MonitorPage', () => { it('renders expected elements for valid props', async () => { const { findByText, findByPlaceholderText } = render(); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/synthetics/checks_navigation.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/synthetics/checks_navigation.tsx index f9d98b7b640c6..f9573da57d914 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/synthetics/checks_navigation.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/synthetics/checks_navigation.tsx @@ -6,13 +6,18 @@ */ import React from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiText, + useIsWithinMaxBreakpoint, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useHistory } from 'react-router-dom'; import moment from 'moment'; import { SyntheticsJourneyApiResponse } from '../../../../common/runtime_types/ping'; import { getShortTimeStamp } from '../../components/overview/monitor_list/columns/monitor_status_column'; -import { useBreakpoints } from '../../../hooks/use_breakpoints'; interface Props { timestamp: string; @@ -21,9 +26,7 @@ interface Props { export const ChecksNavigation = ({ timestamp, details }: Props) => { const history = useHistory(); - const { down } = useBreakpoints(); - - const isMobile = down('s'); + const isMobile = useIsWithinMaxBreakpoint('s'); return ( diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts index 02857fafb69a9..67ac37e4b101d 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts @@ -35,7 +35,7 @@ export const setMonitor = async ({ id?: string; }): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitor> => { if (id) { - return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`); + return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor); } else { return await apiService.post(API_URLS.SYNTHETICS_MONITORS, monitor, undefined, { preserve_namespace: true, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/synthetic_journey_blocks.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/synthetic_journey_blocks.ts index 6ffbeb6978f75..9c020db333bb9 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/synthetic_journey_blocks.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/synthetic_journey_blocks.ts @@ -7,16 +7,18 @@ import { Action } from 'redux-actions'; import { call, fork, put, select, takeEvery, throttle } from 'redux-saga/effects'; -import { ScreenshotBlockDoc } from '../../../../common/runtime_types/ping/synthetics'; +import { + ScreenshotBlockDoc, + ScreenshotBlockCache, + isPendingBlock, +} from '../../../../common/runtime_types'; import { fetchScreenshotBlockSet } from '../api/journey'; import { fetchBlocksAction, setBlockLoadingAction, - isPendingBlock, pruneCacheAction, putBlocksAction, putCacheSize, - ScreenshotBlockCache, updateHitCountsAction, } from '../reducers/synthetics'; import { syntheticsSelector } from '../selectors'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.test.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.test.ts index 06d738d01b42f..1e38c89dc8208 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.test.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.test.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { isPendingBlock } from '../../../../common/runtime_types'; import { fetchBlocksAction, - isPendingBlock, pruneCacheAction, setBlockLoadingAction, putBlocksAction, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.ts index 2a0cf7188a9e8..c523e72b64977 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/synthetics.ts @@ -9,20 +9,9 @@ import { createAction, handleActions, Action } from 'redux-actions'; import { isScreenshotBlockDoc, ScreenshotBlockDoc, + ScreenshotBlockCache, } from '../../../../common/runtime_types/ping/synthetics'; -export interface PendingBlock { - status: 'pending' | 'loading'; -} - -export function isPendingBlock(data: unknown): data is PendingBlock { - return ['pending', 'loading'].some((s) => s === (data as PendingBlock)?.status); -} -export type StoreScreenshotBlock = ScreenshotBlockDoc | PendingBlock; -export interface ScreenshotBlockCache { - [hash: string]: StoreScreenshotBlock; -} - export interface CacheHitCount { hash: string; hitTime: number; diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index 8557f954e713b..f795523cf0fbb 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -41,6 +41,8 @@ import { CasesUiStart } from '@kbn/cases-plugin/public'; import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { PLUGIN } from '../common/constants/plugin'; import { OVERVIEW_ROUTE } from '../common/constants/ui'; import { @@ -55,6 +57,7 @@ import { } from './legacy_uptime/lib/alert_types'; import { monitorDetailNavigatorParams } from './apps/locators/monitor_detail'; import { editMonitorNavigatorParams } from './apps/locators/edit_monitor'; +import { setStartServices } from './kibana_services'; export interface ClientPluginsSetup { home?: HomePublicPluginSetup; @@ -79,6 +82,13 @@ export interface ClientPluginsStart { dataViews: DataViewsPublicPluginStart; spaces: SpacesPluginStart; cloud?: CloudStart; + appName: string; + storage: IStorageWrapper; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + docLinks: DocLinksStart; + uiSettings: CoreStart['uiSettings']; + usageCollection: UsageCollectionStart; } export interface UptimePluginServices extends Partial { @@ -228,6 +238,7 @@ export class UptimePlugin public start(start: CoreStart, plugins: ClientPluginsStart): void { if (plugins.fleet) { const { registerExtension } = plugins.fleet; + setStartServices(start); registerExtension({ package: 'synthetics', diff --git a/x-pack/plugins/synthetics/server/common/pings/query_pings.ts b/x-pack/plugins/synthetics/server/common/pings/query_pings.ts new file mode 100644 index 0000000000000..b6d1b42923928 --- /dev/null +++ b/x-pack/plugins/synthetics/server/common/pings/query_pings.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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { UMElasticsearchQueryFn } from '../../legacy_uptime/lib/adapters/framework'; +import { + GetPingsParams, + HttpResponseBody, + PingsResponse, + Ping, +} from '../../../common/runtime_types'; + +const DEFAULT_PAGE_SIZE = 25; + +/** + * This branch of filtering is used for monitors of type `browser`. This monitor + * type represents an unbounded set of steps, with each `check_group` representing + * a distinct journey. The document containing the `summary` field is indexed last, and + * contains the data necessary for querying a journey. + * + * Because of this, when querying for "pings", it is important that we treat `browser` summary + * checks as the "ping" we want. Without this filtering, we will receive >= N pings for a journey + * of N steps, because an individual step may also contain multiple documents. + */ +const REMOVE_NON_SUMMARY_BROWSER_CHECKS = { + must_not: [ + { + bool: { + filter: [ + { + term: { + 'monitor.type': 'browser', + }, + }, + { + bool: { + must_not: [ + { + exists: { + field: 'summary', + }, + }, + ], + }, + }, + ], + }, + }, + ], +}; + +function isStringArray(value: unknown): value is string[] { + if (!Array.isArray(value)) return false; + // are all array items strings + if (!value.some((s) => typeof s !== 'string')) return true; + throw Error('Excluded locations can only be strings'); +} + +export const queryPings: UMElasticsearchQueryFn = async ({ + uptimeEsClient, + dateRange: { from, to }, + index, + monitorId, + status, + sort, + size: sizeParam, + locations, + excludedLocations, +}) => { + const size = sizeParam ?? DEFAULT_PAGE_SIZE; + + const searchBody = { + size, + ...(index ? { from: index * size } : {}), + query: { + bool: { + filter: [ + { range: { '@timestamp': { gte: from, lte: to } } }, + ...(monitorId ? [{ term: { config_id: monitorId } }] : []), + ...(status ? [{ term: { 'monitor.status': status } }] : []), + ] as QueryDslQueryContainer[], + ...REMOVE_NON_SUMMARY_BROWSER_CHECKS, + }, + }, + sort: [{ '@timestamp': { order: (sort ?? 'desc') as 'asc' | 'desc' } }], + ...((locations ?? []).length > 0 + ? { post_filter: { terms: { 'observer.geo.name': locations as unknown as string[] } } } + : {}), + }; + + // if there are excluded locations, add a clause to the query's filter + const excludedLocationsArray: unknown = excludedLocations && JSON.parse(excludedLocations); + if (isStringArray(excludedLocationsArray) && excludedLocationsArray.length > 0) { + searchBody.query.bool.filter.push({ + bool: { + must_not: [ + { + terms: { + 'observer.geo.name': excludedLocationsArray, + }, + }, + ], + }, + }); + } + + const { + body: { + hits: { hits, total }, + }, + } = await uptimeEsClient.search({ body: searchBody }); + + const pings: Ping[] = hits.map((doc: any) => { + const { _id, _source } = doc; + // Calculate here the length of the content string in bytes, this is easier than in client JS, where + // we don't have access to Buffer.byteLength. There are some hacky ways to do this in the + // client but this is cleaner. + const httpBody: HttpResponseBody | undefined = _source?.http?.response?.body; + if (httpBody && httpBody.content) { + httpBody.content_bytes = Buffer.byteLength(httpBody.content); + } + + return { ..._source, timestamp: _source['@timestamp'], docId: _id }; + }); + + return { + total: total.value, + pings, + }; +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot_blocks.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot_blocks.ts index 1de6df8e8dfd9..954fa1d76e113 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot_blocks.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot_blocks.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ScreenshotBlockDoc } from '../../../../common/runtime_types/ping/synthetics'; +import { ScreenshotBlockDoc } from '../../../../common/runtime_types'; import { UMElasticsearchQueryFn } from '../adapters/framework'; interface ScreenshotBlockResultType { diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journeys.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journeys.ts index cd72875cf85fd..2e9be1ddf3f74 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journeys.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journeys.ts @@ -12,7 +12,7 @@ import { API_URLS } from '../../../../common/constants'; export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', - path: API_URLS.JOURNEY_CREATE, + path: API_URLS.JOURNEY, validate: { params: schema.object({ checkGroup: schema.string(), diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index 4190943a1acf2..6e60de0848706 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { createGetMonitorStatusRoute } from './monitor_summary/monitor_status'; import { getAPIKeySyntheticsRoute } from './monitor_cruds/get_api_key'; import { getServiceLocationsRoute } from './synthetics_service/get_service_locations'; import { deleteSyntheticsMonitorRoute } from './monitor_cruds/delete_monitor'; @@ -26,6 +25,7 @@ import { installIndexTemplatesRoute } from './synthetics_service/install_index_t import { editSyntheticsMonitorRoute } from './monitor_cruds/edit_monitor'; import { addSyntheticsMonitorRoute } from './monitor_cruds/add_monitor'; import { addSyntheticsProjectMonitorRoute } from './monitor_cruds/add_monitor_project'; +import { syntheticsGetPingsRoute } from './pings'; import { SyntheticsRestApiRouteFactory, SyntheticsStreamingRouteFactory, @@ -47,7 +47,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ testNowMonitorRoute, getServiceAllowedRoute, getAPIKeySyntheticsRoute, - createGetMonitorStatusRoute, + syntheticsGetPingsRoute, ]; export const syntheticsAppStreamingApiRoutes: SyntheticsStreamingRouteFactory[] = [ diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts index 8b81e34840c66..668d97a0819e3 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts @@ -6,11 +6,12 @@ */ import { schema } from '@kbn/config-schema'; import { UMServerLibs } from '../../legacy_uptime/lib/lib'; -import { ProjectBrowserMonitor } from '../../../common/runtime_types'; +import { ProjectMonitor } from '../../../common/runtime_types'; + import { SyntheticsStreamingRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; import { getAllLocations } from '../../synthetics_service/get_all_locations'; -import { ProjectMonitorFormatter } from '../../synthetics_service/project_monitor_formatter'; +import { ProjectMonitorFormatter } from '../../synthetics_service/project_monitor/project_monitor_formatter'; export const addSyntheticsProjectMonitorRoute: SyntheticsStreamingRouteFactory = ( libs: UMServerLibs @@ -32,7 +33,7 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsStreamingRouteFactory = subject, }): Promise => { try { - const monitors = (request.body?.monitors as ProjectBrowserMonitor[]) || []; + const monitors = (request.body?.monitors as ProjectMonitor[]) || []; const spaceId = server.spaces.spacesService.getSpaceId(request); const { keep_stale: keepStale, project: projectId } = request.body || {}; const { publicLocations, privateLocations } = await getAllLocations( diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/delete_monitor_bulk.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/delete_monitor_bulk.ts new file mode 100644 index 0000000000000..1eed2836778c3 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/delete_monitor_bulk.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { SavedObjectsClientContract, KibanaRequest } from '@kbn/core/server'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { + formatTelemetryDeleteEvent, + sendTelemetryEvents, +} from '../../telemetry/monitor_upgrade_sender'; +import { ConfigKey, MonitorFields, SyntheticsMonitor } from '../../../../common/runtime_types'; +import { UptimeServerSetup } from '../../../legacy_uptime/lib/adapters'; +import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; +import { syntheticsMonitorType } from '../../../../common/types/saved_objects'; + +export const deleteMonitorBulk = async ({ + savedObjectsClient, + server, + monitors, + syntheticsMonitorClient, + request, +}: { + savedObjectsClient: SavedObjectsClientContract; + server: UptimeServerSetup; + monitors: Array>; + syntheticsMonitorClient: SyntheticsMonitorClient; + request: KibanaRequest; +}) => { + const { logger, telemetry, kibanaVersion } = server; + const spaceId = server.spaces.spacesService.getSpaceId(request); + + try { + const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors( + monitors.map((normalizedMonitor) => ({ + ...normalizedMonitor.attributes, + id: + (normalizedMonitor.attributes as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] || + normalizedMonitor.id, + })), + request, + savedObjectsClient, + spaceId + ); + const deletePromises = monitors.map((monitor) => + savedObjectsClient.delete(syntheticsMonitorType, monitor.id) + ); + + const [errors] = await Promise.all([deleteSyncPromise, ...deletePromises]); + + monitors.forEach((monitor) => { + sendTelemetryEvents( + logger, + telemetry, + formatTelemetryDeleteEvent( + monitor, + kibanaVersion, + new Date().toISOString(), + Boolean((monitor.attributes as MonitorFields)[ConfigKey.SOURCE_INLINE]), + errors + ) + ); + }); + + return errors; + } catch (e) { + throw e; + } +}; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts new file mode 100644 index 0000000000000..5c28bdab9d3d1 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + SavedObjectsUpdateResponse, + SavedObject, + SavedObjectsClientContract, + KibanaRequest, +} from '@kbn/core/server'; +import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; +import { + MonitorFields, + EncryptedSyntheticsMonitor, + SyntheticsMonitorWithSecrets, + SyntheticsMonitor, + ConfigKey, + PrivateLocation, +} from '../../../../common/runtime_types'; +import { syntheticsMonitorType } from '../../../legacy_uptime/lib/saved_objects/synthetics_monitor'; +import { + sendTelemetryEvents, + formatTelemetryUpdateEvent, +} from '../../telemetry/monitor_upgrade_sender'; +import type { UptimeServerSetup } from '../../../legacy_uptime/lib/adapters/framework'; + +// Simplify return promise type and type it with runtime_types + +export const syncEditedMonitorBulk = async ({ + server, + request, + spaceId, + monitorsToUpdate, + savedObjectsClient, + privateLocations, + syntheticsMonitorClient, +}: { + monitorsToUpdate: Array<{ + normalizedMonitor: SyntheticsMonitor; + monitorWithRevision: SyntheticsMonitorWithSecrets; + previousMonitor: SavedObject; + decryptedPreviousMonitor: SavedObject; + }>; + server: UptimeServerSetup; + syntheticsMonitorClient: SyntheticsMonitorClient; + savedObjectsClient: SavedObjectsClientContract; + request: KibanaRequest; + privateLocations: PrivateLocation[]; + spaceId: string; +}) => { + let savedObjectsSuccessful = false; + let syncSuccessful = false; + + try { + async function updateSavedObjects() { + try { + const editedSOPromise = await savedObjectsClient.bulkUpdate( + monitorsToUpdate.map(({ previousMonitor, monitorWithRevision }) => ({ + type: syntheticsMonitorType, + id: previousMonitor.id, + attributes: monitorWithRevision, + })) + ); + savedObjectsSuccessful = true; + return editedSOPromise; + } catch (e) { + savedObjectsSuccessful = false; + } + } + + async function syncUpdatedMonitors() { + try { + const editSyncPromise = await syntheticsMonitorClient.editMonitors( + monitorsToUpdate.map(({ normalizedMonitor, previousMonitor }) => ({ + monitor: normalizedMonitor as MonitorFields, + id: previousMonitor.id, + previousMonitor, + })), + request, + savedObjectsClient, + privateLocations, + spaceId + ); + syncSuccessful = true; + return editSyncPromise; + } catch (e) { + syncSuccessful = false; + } + } + + const [editedMonitorSavedObjects, errors] = await Promise.all([ + updateSavedObjects(), + syncUpdatedMonitors(), + ]); + + monitorsToUpdate.forEach(({ normalizedMonitor, previousMonitor }) => { + const editedMonitorSavedObject = editedMonitorSavedObjects?.saved_objects.find( + (obj) => obj.id === previousMonitor.id + ); + + sendTelemetryEvents( + server.logger, + server.telemetry, + formatTelemetryUpdateEvent( + editedMonitorSavedObject as SavedObjectsUpdateResponse, + previousMonitor, + server.kibanaVersion, + Boolean((normalizedMonitor as MonitorFields)[ConfigKey.SOURCE_INLINE]), + errors + ) + ); + }); + + return { errors, editedMonitors: editedMonitorSavedObjects?.saved_objects }; + } catch (e) { + server.logger.error(`Unable to update Synthetics monitors `); + + if (!syncSuccessful && savedObjectsSuccessful) { + await savedObjectsClient.bulkUpdate( + monitorsToUpdate.map(({ previousMonitor, decryptedPreviousMonitor }) => ({ + type: syntheticsMonitorType, + id: previousMonitor.id, + attributes: decryptedPreviousMonitor.attributes, + })) + ); + } + + throw e; + } +}; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts index fb0a1fe3634db..de1b0b90ef95c 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts @@ -109,13 +109,15 @@ export const deleteMonitor = async ({ normalizedMonitor = normalizeSecrets(monitor); - const deleteSyncPromise = syntheticsMonitorClient.deleteMonitor( - { - ...normalizedMonitor.attributes, - id: - (normalizedMonitor.attributes as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] || - monitorId, - }, + const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors( + [ + { + ...normalizedMonitor.attributes, + id: + (normalizedMonitor.attributes as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] || + monitorId, + }, + ], request, savedObjectsClient, spaceId diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts index d0cb2fe02d4fa..9f4492ce2dc70 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts @@ -49,6 +49,7 @@ describe('syncEditedMonitor', () => { }, packagePolicyService: { get: jest.fn().mockReturnValue({}), + getByIDs: jest.fn().mockReturnValue([]), buildPackagePolicyFromPackage: jest.fn().mockReturnValue({}), }, }, @@ -79,7 +80,9 @@ describe('syncEditedMonitor', () => { const previousMonitor = { id: 'saved-obj-id', - attributes: { name: editedMonitor.name }, + attributes: { name: editedMonitor.name, locations: [] } as any, + type: 'synthetics-monitor', + references: [], } as SavedObject; const syntheticsService = new SyntheticsService(serverMock); @@ -104,9 +107,11 @@ describe('syncEditedMonitor', () => { }); expect(syntheticsService.editConfig).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'saved-obj-id', - }) + expect.arrayContaining([ + expect.objectContaining({ + id: 'saved-obj-id', + }), + ]) ); expect(serverMock.authSavedObjectsClient?.update).toHaveBeenCalledWith( diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts index 5dc77816f4aa3..a16a1ba7089e5 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -13,6 +13,7 @@ import { KibanaRequest, } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { getSyntheticsPrivateLocations } from '../../legacy_uptime/lib/saved_objects/private_locations'; import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; import { MonitorFields, @@ -153,11 +154,13 @@ export const syncEditedMonitor = async ({ monitorWithRevision ); - const editSyncPromise = syntheticsMonitorClient.editMonitor( - normalizedMonitor as MonitorFields, - previousMonitor.id, + const allPrivateLocations = await getSyntheticsPrivateLocations(savedObjectsClient); + + const editSyncPromise = syntheticsMonitorClient.editMonitors( + [{ monitor: normalizedMonitor as MonitorFields, id: previousMonitor.id, previousMonitor }], request, savedObjectsClient, + allPrivateLocations, spaceId ); diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.ts index 88ae4690dd4bd..d648722e0d5cd 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.ts @@ -10,8 +10,8 @@ import { formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { BrowserFieldsCodec, - ProjectBrowserMonitorCodec, - ProjectBrowserMonitor, + ProjectMonitorCodec, + ProjectMonitor, ConfigKey, DataStream, DataStreamCodec, @@ -34,16 +34,18 @@ const monitorTypeToCodecMap: Record = { [DataStream.BROWSER]: BrowserFieldsCodec, }; -/** - * Validates monitor fields with respect to the relevant Codec identified by object's 'type' property. - * @param monitorFields {MonitorFields} The mixed type representing the possible monitor payload from UI. - */ -export function validateMonitor(monitorFields: MonitorFields): { +export interface ValidationResult { valid: boolean; reason: string; details: string; payload: object; -} { +} + +/** + * Validates monitor fields with respect to the relevant Codec identified by object's 'type' property. + * @param monitorFields {MonitorFields} The mixed type representing the possible monitor payload from UI. + */ +export function validateMonitor(monitorFields: MonitorFields): ValidationResult { const { [ConfigKey.MONITOR_TYPE]: monitorType } = monitorFields; const decodedType = DataStreamCodec.decode(monitorType); @@ -82,15 +84,7 @@ export function validateMonitor(monitorFields: MonitorFields): { return { valid: true, reason: '', details: '', payload: monitorFields }; } -export function validateProjectMonitor( - monitorFields: ProjectBrowserMonitor, - projectId: string -): { - valid: boolean; - reason: string; - details: string; - payload: object; -} { +export function validateProjectMonitor(monitorFields: ProjectMonitor): ValidationResult { const locationsError = monitorFields.locations && monitorFields.locations.length === 0 && @@ -98,7 +92,7 @@ export function validateProjectMonitor( ? 'Invalid value "[]" supplied to field "locations"' : ''; // Cast it to ICMPCodec to satisfy typing. During runtime, correct codec will be used to decode. - const decodedMonitor = ProjectBrowserMonitorCodec.decode(monitorFields); + const decodedMonitor = ProjectMonitorCodec.decode(monitorFields); if (isLeft(decodedMonitor)) { return { diff --git a/x-pack/plugins/synthetics/server/routes/monitor_summary/monitor_status.ts b/x-pack/plugins/synthetics/server/routes/monitor_summary/monitor_status.ts deleted file mode 100644 index 885bd60bf452a..0000000000000 --- a/x-pack/plugins/synthetics/server/routes/monitor_summary/monitor_status.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 { schema, TypeOf } from '@kbn/config-schema'; -import { UMServerLibs } from '../../legacy_uptime/uptime_server'; -import { syntheticsMonitorType } from '../../../common/types/saved_objects'; -import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes'; -import { SYNTHETICS_API_URLS } from '../../../common/constants'; -import { ConfigKey, MonitorFields } from '../../../common/runtime_types'; - -const queryParams = schema.object({ - monitorId: schema.string(), - dateStart: schema.string(), - dateEnd: schema.string(), -}); - -type QueryParams = TypeOf; - -export const createGetMonitorStatusRoute: SyntheticsRestApiRouteFactory = (libs: UMServerLibs) => ({ - method: 'GET', - path: SYNTHETICS_API_URLS.MONITOR_STATUS, - validate: { - query: queryParams, - }, - handler: async ({ uptimeEsClient, request, server, savedObjectsClient }): Promise => { - const { monitorId, dateStart, dateEnd } = request.query as QueryParams; - - const latestMonitor = await libs.requests.getLatestMonitor({ - uptimeEsClient, - monitorId, - dateStart, - dateEnd, - }); - - if (latestMonitor.docId) { - return latestMonitor; - } - - if (!server.savedObjectsClient) { - return null; - } - - try { - const { - saved_objects: [monitorSavedObject], - } = await savedObjectsClient.find({ - type: syntheticsMonitorType, - perPage: 1, - page: 1, - filter: `${syntheticsMonitorType}.id: "${syntheticsMonitorType}:${monitorId}" OR ${syntheticsMonitorType}.attributes.${ConfigKey.CUSTOM_HEARTBEAT_ID}: "${monitorId}"`, - }); - - if (!monitorSavedObject) { - return null; - } - - const { - [ConfigKey.URLS]: url, - [ConfigKey.NAME]: name, - [ConfigKey.HOSTS]: host, - [ConfigKey.MONITOR_TYPE]: type, - } = monitorSavedObject.attributes as Partial; - - return { - url: { - full: url || host, - }, - monitor: { - name, - type, - id: monitorSavedObject.id, - }, - }; - } catch (e) { - server.logger.error(e); - } - }, -}); diff --git a/x-pack/plugins/synthetics/server/routes/pings/get_pings.ts b/x-pack/plugins/synthetics/server/routes/pings/get_pings.ts new file mode 100644 index 0000000000000..e94c928caed53 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/pings/get_pings.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { SYNTHETICS_API_URLS } from '../../../common/constants'; +import { UMServerLibs } from '../../legacy_uptime/lib/lib'; +import { UMRestApiRouteFactory } from '../../legacy_uptime/routes/types'; +import { queryPings } from '../../common/pings/query_pings'; + +export const syntheticsGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.PINGS, + validate: { + query: schema.object({ + from: schema.string(), + to: schema.string(), + locations: schema.maybe(schema.string()), + excludedLocations: schema.maybe(schema.string()), + monitorId: schema.maybe(schema.string()), + index: schema.maybe(schema.number()), + size: schema.maybe(schema.number()), + sort: schema.maybe(schema.string()), + status: schema.maybe(schema.string()), + }), + }, + handler: async ({ uptimeEsClient, request, response }): Promise => { + const { from, to, index, monitorId, status, sort, size, locations, excludedLocations } = + request.query; + + return await queryPings({ + uptimeEsClient, + dateRange: { from, to }, + index, + monitorId, + status, + sort, + size, + locations: locations ? JSON.parse(locations) : [], + excludedLocations, + }); + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/pings/index.ts b/x-pack/plugins/synthetics/server/routes/pings/index.ts new file mode 100644 index 0000000000000..7bc2a27c155bb --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/pings/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 { syntheticsGetPingsRoute } from './get_pings'; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts index 7e8c75a708430..9c9d24a3f58f4 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/browser.ts @@ -66,7 +66,6 @@ export const browserFormatters: BrowserFormatMap = { [ConfigKey.JOURNEY_FILTERS_TAGS]: (fields) => arrayFormatter(fields[ConfigKey.JOURNEY_FILTERS_TAGS]), [ConfigKey.IGNORE_HTTPS_ERRORS]: null, - [ConfigKey.JOURNEY_ID]: null, [ConfigKey.PROJECT_ID]: null, [ConfigKey.PLAYWRIGHT_OPTIONS]: (fields) => stringToObjectFormatter(fields[ConfigKey.PLAYWRIGHT_OPTIONS] || ''), diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts index 0163ee52981e5..16a0829cbb710 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts @@ -30,6 +30,7 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.MONITOR_SOURCE_TYPE]: (fields) => fields[ConfigKey.MONITOR_SOURCE_TYPE] || SourceType.UI, [ConfigKey.FORM_MONITOR_TYPE]: null, + [ConfigKey.JOURNEY_ID]: null, }; export const arrayFormatter = (value: string[] = []) => (value.length ? value : null); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.test.ts deleted file mode 100644 index 3362585a523c1..0000000000000 --- a/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.test.ts +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - DataStream, - ScreenshotOption, - Locations, - LocationStatus, - ProjectBrowserMonitor, - PrivateLocation, -} from '../../../common/runtime_types'; -import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults'; -import { normalizeProjectMonitors } from './browser'; - -describe('browser normalizers', () => { - describe('normalize push monitors', () => { - const playwrightOptions = { - headless: true, - }; - const params = { - url: 'test-url', - }; - const projectId = 'test-project-id'; - const locations: Locations = [ - { - id: 'us_central', - label: 'Test Location', - geo: { lat: 33.333, lon: 73.333 }, - url: 'test-url', - isServiceManaged: true, - status: LocationStatus.GA, - }, - { - id: 'us_east', - label: 'Test Location', - geo: { lat: 33.333, lon: 73.333 }, - url: 'test-url', - isServiceManaged: true, - status: LocationStatus.GA, - }, - ]; - const privateLocations: PrivateLocation[] = [ - { - id: 'germany', - label: 'Germany', - concurrentMonitors: 1, - agentPolicyId: 'germany', - }, - ]; - const monitors: ProjectBrowserMonitor[] = [ - { - id: 'test-id-1', - screenshot: ScreenshotOption.OFF, - name: 'test-name-1', - content: 'test content 1', - schedule: 3, - throttling: { - latency: 20, - upload: 10, - download: 5, - }, - locations: ['us_central'], - tags: ['tag1', 'tag2'], - ignoreHTTPSErrors: true, - apmServiceName: 'cart-service', - }, - { - id: 'test-id-2', - screenshot: ScreenshotOption.ON, - name: 'test-name-2', - content: 'test content 2', - schedule: 10, - throttling: { - latency: 18, - upload: 15, - download: 10, - }, - params: {}, - playwrightOptions: {}, - locations: ['us_central', 'us_east'], - tags: ['tag3', 'tag4'], - ignoreHTTPSErrors: false, - apmServiceName: 'bean-service', - }, - { - id: 'test-id-3', - screenshot: ScreenshotOption.ON, - name: 'test-name-3', - content: 'test content 3', - schedule: 10, - throttling: { - latency: 18, - upload: 15, - download: 10, - }, - params, - playwrightOptions, - locations: ['us_central', 'us_east'], - privateLocations: ['Germany'], - tags: ['tag3', 'tag4'], - ignoreHTTPSErrors: false, - apmServiceName: 'bean-service', - }, - ]; - - it('properly normalizes browser monitor', () => { - const actual = normalizeProjectMonitors({ - locations, - privateLocations, - monitors, - projectId, - namespace: 'test-space', - }); - expect(actual).toEqual([ - { - ...DEFAULT_FIELDS[DataStream.BROWSER], - journey_id: 'test-id-1', - ignore_https_errors: true, - origin: 'project', - locations: [ - { - geo: { - lat: 33.333, - lon: 73.333, - }, - id: 'us_central', - isServiceManaged: true, - label: 'Test Location', - url: 'test-url', - status: 'ga', - }, - ], - name: 'test-name-1', - schedule: { - number: '3', - unit: 'm', - }, - screenshots: 'off', - 'service.name': 'cart-service', - 'source.project.content': 'test content 1', - tags: ['tag1', 'tag2'], - 'throttling.config': '5d/10u/20l', - 'throttling.download_speed': '5', - 'throttling.is_enabled': true, - 'throttling.latency': '20', - 'throttling.upload_speed': '10', - params: '', - type: 'browser', - project_id: projectId, - namespace: 'test_space', - original_space: 'test-space', - custom_heartbeat_id: 'test-id-1-test-project-id-test-space', - timeout: null, - }, - { - ...DEFAULT_FIELDS[DataStream.BROWSER], - journey_id: 'test-id-2', - ignore_https_errors: false, - origin: 'project', - locations: [ - { - geo: { - lat: 33.333, - lon: 73.333, - }, - id: 'us_central', - isServiceManaged: true, - label: 'Test Location', - url: 'test-url', - status: 'ga', - }, - { - geo: { - lat: 33.333, - lon: 73.333, - }, - id: 'us_east', - isServiceManaged: true, - label: 'Test Location', - url: 'test-url', - status: 'ga', - }, - ], - name: 'test-name-2', - params: '', - playwright_options: '', - schedule: { - number: '10', - unit: 'm', - }, - screenshots: 'on', - 'service.name': 'bean-service', - 'source.project.content': 'test content 2', - tags: ['tag3', 'tag4'], - 'throttling.config': '10d/15u/18l', - 'throttling.download_speed': '10', - 'throttling.is_enabled': true, - 'throttling.latency': '18', - 'throttling.upload_speed': '15', - type: 'browser', - project_id: projectId, - namespace: 'test_space', - original_space: 'test-space', - custom_heartbeat_id: 'test-id-2-test-project-id-test-space', - timeout: null, - }, - { - ...DEFAULT_FIELDS[DataStream.BROWSER], - journey_id: 'test-id-3', - ignore_https_errors: false, - origin: 'project', - locations: [ - { - geo: { - lat: 33.333, - lon: 73.333, - }, - id: 'us_central', - isServiceManaged: true, - label: 'Test Location', - url: 'test-url', - status: 'ga', - }, - { - geo: { - lat: 33.333, - lon: 73.333, - }, - id: 'us_east', - isServiceManaged: true, - label: 'Test Location', - url: 'test-url', - status: 'ga', - }, - privateLocations[0], - ], - name: 'test-name-3', - params: JSON.stringify(params), - playwright_options: JSON.stringify(playwrightOptions), - schedule: { - number: '10', - unit: 'm', - }, - screenshots: 'on', - 'service.name': 'bean-service', - 'source.project.content': 'test content 3', - tags: ['tag3', 'tag4'], - 'throttling.config': '10d/15u/18l', - 'throttling.download_speed': '10', - 'throttling.is_enabled': true, - 'throttling.latency': '18', - 'throttling.upload_speed': '15', - type: 'browser', - project_id: projectId, - namespace: 'test_space', - original_space: 'test-space', - custom_heartbeat_id: 'test-id-3-test-project-id-test-space', - timeout: null, - }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.ts b/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.ts deleted file mode 100644 index 3b5d684fe0f58..0000000000000 --- a/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { PrivateLocation } from '../../../common/runtime_types'; -import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults'; -import { formatKibanaNamespace } from '../../../common/formatters'; -import { - BrowserFields, - ConfigKey, - DataStream, - FormMonitorType, - Locations, - ProjectBrowserMonitor, - ScheduleUnit, - SourceType, -} from '../../../common/runtime_types/monitor_management'; - -/* Represents all of the push-monitor related fields that need to be - * normalized. Excludes fields that we do not support for push monitors - * This type ensures that contributors remember to add normalizers for push - * monitors where appropriate when new keys are added to browser montiors */ -type NormalizedPublicFields = Omit< - BrowserFields, - | ConfigKey.METADATA - | ConfigKey.SOURCE_INLINE - | ConfigKey.SOURCE_ZIP_URL - | ConfigKey.SOURCE_ZIP_USERNAME - | ConfigKey.SOURCE_ZIP_PASSWORD - | ConfigKey.SOURCE_ZIP_FOLDER - | ConfigKey.SOURCE_ZIP_PROXY_URL - | ConfigKey.ZIP_URL_TLS_CERTIFICATE_AUTHORITIES - | ConfigKey.ZIP_URL_TLS_CERTIFICATE - | ConfigKey.ZIP_URL_TLS_KEY - | ConfigKey.ZIP_URL_TLS_KEY_PASSPHRASE - | ConfigKey.ZIP_URL_TLS_VERIFICATION_MODE - | ConfigKey.ZIP_URL_TLS_VERSION - | ConfigKey.JOURNEY_FILTERS_TAGS - | ConfigKey.SYNTHETICS_ARGS - | ConfigKey.PORT - | ConfigKey.URLS ->; - -export const normalizeProjectMonitor = ({ - locations = [], - privateLocations = [], - monitor, - projectId, - namespace, -}: { - locations: Locations; - privateLocations: PrivateLocation[]; - monitor: ProjectBrowserMonitor; - projectId: string; - namespace: string; -}): BrowserFields => { - const defaultFields = DEFAULT_FIELDS[DataStream.BROWSER]; - const normalizedFields: NormalizedPublicFields = { - [ConfigKey.MONITOR_TYPE]: DataStream.BROWSER, - [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP, - [ConfigKey.MONITOR_SOURCE_TYPE]: SourceType.PROJECT, - [ConfigKey.NAME]: monitor.name || '', - [ConfigKey.SCHEDULE]: { - number: `${monitor.schedule}`, - unit: ScheduleUnit.MINUTES, - }, - [ConfigKey.PROJECT_ID]: projectId || defaultFields[ConfigKey.PROJECT_ID], - [ConfigKey.JOURNEY_ID]: monitor.id || defaultFields[ConfigKey.JOURNEY_ID], - [ConfigKey.SOURCE_PROJECT_CONTENT]: - monitor.content || defaultFields[ConfigKey.SOURCE_PROJECT_CONTENT], - [ConfigKey.LOCATIONS]: getMonitorLocations({ - monitor, - privateLocations, - publicLocations: locations, - }), - [ConfigKey.THROTTLING_CONFIG]: monitor.throttling - ? `${monitor.throttling.download}d/${monitor.throttling.upload}u/${monitor.throttling.latency}l` - : defaultFields[ConfigKey.THROTTLING_CONFIG], - [ConfigKey.DOWNLOAD_SPEED]: `${ - monitor.throttling?.download || defaultFields[ConfigKey.DOWNLOAD_SPEED] - }`, - [ConfigKey.UPLOAD_SPEED]: `${ - monitor.throttling?.upload || defaultFields[ConfigKey.UPLOAD_SPEED] - }`, - [ConfigKey.IS_THROTTLING_ENABLED]: - Boolean(monitor.throttling) || defaultFields[ConfigKey.IS_THROTTLING_ENABLED], - [ConfigKey.LATENCY]: `${monitor.throttling?.latency || defaultFields[ConfigKey.LATENCY]}`, - [ConfigKey.APM_SERVICE_NAME]: - monitor.apmServiceName || defaultFields[ConfigKey.APM_SERVICE_NAME], - [ConfigKey.IGNORE_HTTPS_ERRORS]: - monitor.ignoreHTTPSErrors || defaultFields[ConfigKey.IGNORE_HTTPS_ERRORS], - [ConfigKey.SCREENSHOTS]: monitor.screenshot || defaultFields[ConfigKey.SCREENSHOTS], - [ConfigKey.TAGS]: monitor.tags || defaultFields[ConfigKey.TAGS], - [ConfigKey.PLAYWRIGHT_OPTIONS]: Object.keys(monitor.playwrightOptions || {}).length - ? JSON.stringify(monitor.playwrightOptions) - : defaultFields[ConfigKey.PLAYWRIGHT_OPTIONS], - [ConfigKey.PARAMS]: Object.keys(monitor.params || {}).length - ? JSON.stringify(monitor.params) - : defaultFields[ConfigKey.PARAMS], - [ConfigKey.JOURNEY_FILTERS_MATCH]: - monitor.filter?.match || defaultFields[ConfigKey.JOURNEY_FILTERS_MATCH], - [ConfigKey.NAMESPACE]: formatKibanaNamespace(namespace) || defaultFields[ConfigKey.NAMESPACE], - [ConfigKey.ORIGINAL_SPACE]: namespace || defaultFields[ConfigKey.ORIGINAL_SPACE], - [ConfigKey.CUSTOM_HEARTBEAT_ID]: `${monitor.id}-${projectId}-${namespace}`, - [ConfigKey.TIMEOUT]: null, - [ConfigKey.ENABLED]: monitor.enabled ?? defaultFields[ConfigKey.ENABLED], - }; - return { - ...DEFAULT_FIELDS[DataStream.BROWSER], - ...normalizedFields, - }; -}; - -export const normalizeProjectMonitors = ({ - locations = [], - privateLocations = [], - monitors = [], - projectId, - namespace, -}: { - locations: Locations; - privateLocations: PrivateLocation[]; - monitors: ProjectBrowserMonitor[]; - projectId: string; - namespace: string; -}) => { - return monitors.map((monitor) => { - return normalizeProjectMonitor({ monitor, locations, privateLocations, projectId, namespace }); - }); -}; - -export const getMonitorLocations = ({ - privateLocations, - publicLocations, - monitor, -}: { - monitor: ProjectBrowserMonitor; - privateLocations: PrivateLocation[]; - publicLocations: Locations; -}) => { - const publicLocs = - monitor.locations?.map((id) => { - return publicLocations.find((location) => location.id === id); - }) || []; - const privateLocs = - monitor.privateLocations?.map((locationName) => { - return privateLocations.find( - (location) => location.label.toLowerCase() === locationName.toLowerCase() - ); - }) || []; - return [...publicLocs, ...privateLocs].filter( - (location) => location !== undefined - ) as BrowserFields[ConfigKey.LOCATIONS]; -}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts index a7d4771995948..8dc6688a70f3f 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts @@ -123,7 +123,7 @@ describe('SyntheticsPrivateLocation', () => { [true, 'Unable to create Synthetics package policy for private location'], [ false, - 'Unable to update Synthetics package policy for monitor Test Monitor. Fleet write permissions are needed to use Synthetics private locations.', + 'Unable to update Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.', ], ])('throws errors for edit monitor', async (writeIntegrationPolicies, error) => { const syntheticsPrivateLocation = new SyntheticsPrivateLocation({ @@ -137,10 +137,11 @@ describe('SyntheticsPrivateLocation', () => { }); try { - await syntheticsPrivateLocation.editMonitor( - testConfig, + await syntheticsPrivateLocation.editMonitors( + [testConfig], {} as unknown as KibanaRequest, savedObjectsClientMock, + [mockPrivateLocation], 'test-space' ); } catch (e) { @@ -168,8 +169,8 @@ describe('SyntheticsPrivateLocation', () => { }, }); try { - await syntheticsPrivateLocation.deleteMonitor( - testConfig, + await syntheticsPrivateLocation.deleteMonitors( + [testConfig], {} as unknown as KibanaRequest, savedObjectsClientMock, 'test-space' diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts index 6d8e6723ca80c..f51f9e479929e 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts @@ -7,6 +7,7 @@ import { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; import { NewPackagePolicy } from '@kbn/fleet-plugin/common'; import { NewPackagePolicyWithId } from '@kbn/fleet-plugin/server/services/package_policy'; +import { cloneDeep } from 'lodash'; import { formatSyntheticsPolicy } from '../../../common/formatters/format_synthetics_policy'; import { getSyntheticsPrivateLocations } from '../../legacy_uptime/lib/saved_objects/private_locations'; import { @@ -42,20 +43,20 @@ export class SyntheticsPrivateLocation { return `${config.id}-${locId}-${spaceId}`; } - async generateNewPolicy( + generateNewPolicy( config: HeartbeatConfig, privateLocation: PrivateLocation, savedObjectsClient: SavedObjectsClientContract, newPolicyTemplate: NewPackagePolicy, spaceId: string - ): Promise { + ): NewPackagePolicy | null { if (!savedObjectsClient) { throw new Error('Could not find savedObjectsClient'); } const { label: locName } = privateLocation; - const newPolicy = { ...newPolicyTemplate }; + const newPolicy = cloneDeep(newPolicyTemplate); try { newPolicy.is_managed = true; @@ -127,7 +128,7 @@ export class SyntheticsPrivateLocation { ); } - const newPolicy = await this.generateNewPolicy( + const newPolicy = this.generateNewPolicy( config, location, savedObjectsClient, @@ -162,83 +163,106 @@ export class SyntheticsPrivateLocation { } } - async editMonitor( - config: HeartbeatConfig, + async editMonitors( + configs: HeartbeatConfig[], request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract, + allPrivateLocations: PrivateLocation[], spaceId: string ) { await this.checkPermissions( request, - `Unable to update Synthetics package policy for monitor ${ - config[ConfigKey.NAME] - }. Fleet write permissions are needed to use Synthetics private locations.` + `Unable to update Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.` ); - const { locations } = config; - - const allPrivateLocations = await getSyntheticsPrivateLocations(savedObjectsClient); - const newPolicyTemplate = await this.buildNewPolicy(savedObjectsClient); if (!newPolicyTemplate) { throw new Error(`Unable to create Synthetics package policy for private location`); } - const monitorPrivateLocations = locations.filter((loc) => !loc.isServiceManaged); + const policiesToUpdate: Array = []; + const policiesToCreate: NewPackagePolicyWithId[] = []; + const policiesToDelete: string[] = []; - for (const privateLocation of allPrivateLocations) { - const hasLocation = monitorPrivateLocations?.some((loc) => loc.id === privateLocation.id); - const currId = this.getPolicyId(config, privateLocation, spaceId); - const hasPolicy = await this.getMonitor(currId, savedObjectsClient); - try { - if (hasLocation) { - const newPolicy = await this.generateNewPolicy( - config, - privateLocation, - savedObjectsClient, - newPolicyTemplate, - spaceId - ); + const existingPolicies = await this.getExistingPolicies( + configs, + allPrivateLocations, + savedObjectsClient, + spaceId + ); - if (!newPolicy) { - throw new Error( - `Unable to ${ - hasPolicy ? 'update' : 'create' - } Synthetics package policy for private location ${privateLocation.label}` - ); - } + for (const config of configs) { + const { locations } = config; - if (hasPolicy) { - await this.updatePolicy(newPolicy, currId, savedObjectsClient); - } else { - await this.createPolicy(newPolicy, currId, savedObjectsClient); - } - } else if (hasPolicy) { - const soClient = savedObjectsClient; - const esClient = this.server.uptimeEsClient.baseESClient; - try { - await this.server.fleet.packagePolicyService.delete(soClient, esClient, [currId], { - force: true, - }); - } catch (e) { - this.server.logger.error(e); - throw new Error( - `Unable to delete Synthetics package policy for monitor ${ - config[ConfigKey.NAME] - } with private location ${privateLocation.label}` + const monitorPrivateLocations = locations.filter((loc) => !loc.isServiceManaged); + + for (const privateLocation of allPrivateLocations) { + const hasLocation = monitorPrivateLocations?.some((loc) => loc.id === privateLocation.id); + const currId = this.getPolicyId(config, privateLocation, spaceId); + const hasPolicy = existingPolicies?.some((policy) => policy.id === currId); + try { + if (hasLocation) { + const newPolicy = this.generateNewPolicy( + config, + privateLocation, + savedObjectsClient, + newPolicyTemplate, + spaceId ); + + if (!newPolicy) { + throw new Error( + `Unable to ${ + hasPolicy ? 'update' : 'create' + } Synthetics package policy for private location ${privateLocation.label}` + ); + } + + if (hasPolicy) { + policiesToUpdate.push({ ...newPolicy, id: currId }); + } else { + policiesToCreate.push({ ...newPolicy, id: currId }); + } + } else if (hasPolicy) { + policiesToDelete.push(currId); } + } catch (e) { + this.server.logger.error(e); + throw new Error( + `Unable to ${hasPolicy ? 'update' : 'create'} Synthetics package policy for monitor ${ + config[ConfigKey.NAME] + } with private location ${privateLocation.label}` + ); } - } catch (e) { - this.server.logger.error(e); - throw new Error( - `Unable to ${hasPolicy ? 'update' : 'create'} Synthetics package policy for monitor ${ - config[ConfigKey.NAME] - } with private location ${privateLocation.label}` - ); } } + + await Promise.all([ + this.createPolicyBulk(policiesToCreate, savedObjectsClient), + this.updatePolicyBulk(policiesToUpdate, savedObjectsClient), + this.deletePolicyBulk(policiesToDelete, savedObjectsClient), + ]); + } + + async getExistingPolicies( + configs: HeartbeatConfig[], + allPrivateLocations: PrivateLocation[], + savedObjectsClient: SavedObjectsClientContract, + spaceId: string + ) { + const listOfPolicies: string[] = []; + for (const config of configs) { + for (const privateLocation of allPrivateLocations) { + const currId = this.getPolicyId(config, privateLocation, spaceId); + listOfPolicies.push(currId); + } + } + return ( + (await this.server.fleet.packagePolicyService.getByIDs(savedObjectsClient, listOfPolicies, { + ignoreMissing: true, + })) ?? [] + ); } async createPolicyBulk( @@ -247,7 +271,7 @@ export class SyntheticsPrivateLocation { ) { const soClient = savedObjectsClient; const esClient = this.server.uptimeEsClient.baseESClient; - if (soClient && esClient) { + if (soClient && esClient && newPolicies.length > 0) { return await this.server.fleet.packagePolicyService.bulkCreate( soClient, esClient, @@ -256,34 +280,35 @@ export class SyntheticsPrivateLocation { } } - async createPolicy( - newPolicy: NewPackagePolicy, - id: string, + async updatePolicyBulk( + updatedPolicies: Array, savedObjectsClient: SavedObjectsClientContract ) { const soClient = savedObjectsClient; const esClient = this.server.uptimeEsClient.baseESClient; - if (soClient && esClient) { - return await this.server.fleet.packagePolicyService.create(soClient, esClient, newPolicy, { - id, - overwrite: true, - }); + if (soClient && esClient && updatedPolicies.length > 0) { + return await this.server.fleet.packagePolicyService.bulkUpdate( + soClient, + esClient, + updatedPolicies, + { + force: true, + } + ); } } - async updatePolicy( - updatedPolicy: NewPackagePolicy, - id: string, + async deletePolicyBulk( + policyIdsToDelete: string[], savedObjectsClient: SavedObjectsClientContract ) { const soClient = savedObjectsClient; const esClient = this.server.uptimeEsClient.baseESClient; - if (soClient && esClient) { - return await this.server.fleet.packagePolicyService.update( + if (soClient && esClient && policyIdsToDelete.length > 0) { + return await this.server.fleet.packagePolicyService.delete( soClient, esClient, - id, - updatedPolicy, + policyIdsToDelete, { force: true, } @@ -291,18 +316,8 @@ export class SyntheticsPrivateLocation { } } - async getMonitor(id: string, savedObjectsClient: SavedObjectsClientContract) { - try { - const soClient = savedObjectsClient; - return await this.server.fleet.packagePolicyService.get(soClient!, id); - } catch (e) { - this.server.logger.debug(e); - return null; - } - } - - async deleteMonitor( - config: HeartbeatConfig, + async deleteMonitors( + configs: HeartbeatConfig[], request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract, spaceId: string @@ -310,42 +325,44 @@ export class SyntheticsPrivateLocation { const soClient = savedObjectsClient; const esClient = this.server.uptimeEsClient.baseESClient; - if (soClient && esClient) { - const { locations } = config; + const allPrivateLocations: PrivateLocation[] = await getSyntheticsPrivateLocations(soClient); - const allPrivateLocations: PrivateLocation[] = await getSyntheticsPrivateLocations(soClient); - - const monitorPrivateLocations = locations.filter((loc) => !loc.isServiceManaged); + if (soClient && esClient) { + const policyIdsToDelete = []; + for (const config of configs) { + const { locations } = config; - for (const privateLocation of monitorPrivateLocations) { - const location = allPrivateLocations?.find((loc) => loc.id === privateLocation.id); - if (location) { - await this.checkPermissions( - request, - `Unable to delete Synthetics package policy for monitor ${ - config[ConfigKey.NAME] - }. Fleet write permissions are needed to use Synthetics private locations.` - ); + const monitorPrivateLocations = locations.filter((loc) => !loc.isServiceManaged); - try { - await this.server.fleet.packagePolicyService.delete( - soClient, - esClient, - [this.getPolicyId(config, location, spaceId)], - { - force: true, - } - ); - } catch (e) { - this.server.logger.error(e); - throw new Error( + for (const privateLocation of monitorPrivateLocations) { + const location = allPrivateLocations?.find((loc) => loc.id === privateLocation.id); + if (location) { + await this.checkPermissions( + request, `Unable to delete Synthetics package policy for monitor ${ config[ConfigKey.NAME] - } with private location ${location.label}` + }. Fleet write permissions are needed to use Synthetics private locations.` ); + try { + policyIdsToDelete.push(this.getPolicyId(config, location, spaceId)); + } catch (e) { + this.server.logger.error(e); + throw new Error( + `Unable to delete Synthetics package policy for monitor ${ + config[ConfigKey.NAME] + } with private location ${location.label}` + ); + } } } } + if (policyIdsToDelete.length > 0) { + await this.checkPermissions( + request, + `Unable to delete Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.` + ); + await this.deletePolicyBulk(policyIdsToDelete, savedObjectsClient); + } } } diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts new file mode 100644 index 0000000000000..ed02f1037e32b --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts @@ -0,0 +1,286 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + DataStream, + ScreenshotOption, + Locations, + LocationStatus, + ProjectMonitor, + PrivateLocation, +} from '../../../../common/runtime_types'; +import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; +import { normalizeProjectMonitors } from '.'; + +describe('browser normalizers', () => { + describe('normalize push monitors', () => { + const playwrightOptions = { + headless: true, + }; + const params = { + url: 'test-url', + }; + const projectId = 'test-project-id'; + const locations: Locations = [ + { + id: 'us_central', + label: 'Test Location', + geo: { lat: 33.333, lon: 73.333 }, + url: 'test-url', + isServiceManaged: true, + status: LocationStatus.GA, + }, + { + id: 'us_east', + label: 'Test Location', + geo: { lat: 33.333, lon: 73.333 }, + url: 'test-url', + isServiceManaged: true, + status: LocationStatus.GA, + }, + ]; + const privateLocations: PrivateLocation[] = [ + { + id: 'germany', + label: 'Germany', + isServiceManaged: false, + concurrentMonitors: 1, + agentPolicyId: 'germany', + }, + ]; + const monitors: ProjectMonitor[] = [ + { + id: 'test-id-1', + screenshot: ScreenshotOption.OFF, + name: 'test-name-1', + content: 'test content 1', + schedule: 3, + throttling: { + latency: 20, + upload: 10, + download: 5, + }, + locations: ['us_central'], + tags: ['tag1', 'tag2'], + ignoreHTTPSErrors: true, + apmServiceName: 'cart-service', + type: DataStream.BROWSER, + }, + { + id: 'test-id-2', + screenshot: ScreenshotOption.ON, + name: 'test-name-2', + content: 'test content 2', + schedule: 10, + throttling: { + latency: 18, + upload: 15, + download: 10, + }, + params: {}, + playwrightOptions: {}, + locations: ['us_central', 'us_east'], + tags: ['tag3', 'tag4'], + ignoreHTTPSErrors: false, + apmServiceName: 'bean-service', + type: DataStream.BROWSER, + }, + { + id: 'test-id-3', + screenshot: ScreenshotOption.ON, + name: 'test-name-3', + content: 'test content 3', + schedule: 10, + throttling: { + latency: 18, + upload: 15, + download: 10, + }, + params, + playwrightOptions, + locations: ['us_central', 'us_east'], + privateLocations: ['Germany'], + tags: ['tag3', 'tag4'], + ignoreHTTPSErrors: false, + apmServiceName: 'bean-service', + type: DataStream.BROWSER, + }, + ]; + + it('properly normalizes browser monitor', () => { + const actual = normalizeProjectMonitors({ + locations, + privateLocations, + monitors, + projectId, + namespace: 'test-space', + }); + expect(actual).toEqual([ + { + normalizedFields: { + ...DEFAULT_FIELDS[DataStream.BROWSER], + journey_id: 'test-id-1', + ignore_https_errors: true, + origin: 'project', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + url: 'test-url', + status: 'ga', + }, + ], + name: 'test-name-1', + schedule: { + number: '3', + unit: 'm', + }, + screenshots: 'off', + 'service.name': 'cart-service', + 'source.project.content': 'test content 1', + tags: ['tag1', 'tag2'], + 'throttling.config': '5d/10u/20l', + 'throttling.download_speed': '5', + 'throttling.is_enabled': true, + 'throttling.latency': '20', + 'throttling.upload_speed': '10', + params: '', + type: 'browser', + project_id: projectId, + namespace: 'test_space', + original_space: 'test-space', + custom_heartbeat_id: 'test-id-1-test-project-id-test-space', + timeout: null, + }, + unsupportedKeys: [], + }, + { + normalizedFields: { + ...DEFAULT_FIELDS[DataStream.BROWSER], + journey_id: 'test-id-2', + ignore_https_errors: false, + origin: 'project', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + url: 'test-url', + status: 'ga', + }, + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_east', + isServiceManaged: true, + label: 'Test Location', + url: 'test-url', + status: 'ga', + }, + ], + name: 'test-name-2', + params: '', + playwright_options: '', + schedule: { + number: '10', + unit: 'm', + }, + screenshots: 'on', + 'service.name': 'bean-service', + 'source.project.content': 'test content 2', + tags: ['tag3', 'tag4'], + 'throttling.config': '10d/15u/18l', + 'throttling.download_speed': '10', + 'throttling.is_enabled': true, + 'throttling.latency': '18', + 'throttling.upload_speed': '15', + type: 'browser', + project_id: projectId, + namespace: 'test_space', + original_space: 'test-space', + custom_heartbeat_id: 'test-id-2-test-project-id-test-space', + timeout: null, + }, + unsupportedKeys: [], + }, + { + normalizedFields: { + ...DEFAULT_FIELDS[DataStream.BROWSER], + journey_id: 'test-id-3', + ignore_https_errors: false, + origin: 'project', + locations: [ + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_central', + isServiceManaged: true, + label: 'Test Location', + url: 'test-url', + status: 'ga', + }, + { + geo: { + lat: 33.333, + lon: 73.333, + }, + id: 'us_east', + isServiceManaged: true, + label: 'Test Location', + url: 'test-url', + status: 'ga', + }, + { + id: 'germany', + isServiceManaged: false, + label: 'Germany', + agentPolicyId: 'germany', + concurrentMonitors: 1, + }, + ], + name: 'test-name-3', + params: JSON.stringify(params), + playwright_options: JSON.stringify(playwrightOptions), + schedule: { + number: '10', + unit: 'm', + }, + screenshots: 'on', + 'service.name': 'bean-service', + 'source.project.content': 'test content 3', + tags: ['tag3', 'tag4'], + 'throttling.config': '10d/15u/18l', + 'throttling.download_speed': '10', + 'throttling.is_enabled': true, + 'throttling.latency': '18', + 'throttling.upload_speed': '15', + type: 'browser', + project_id: projectId, + namespace: 'test_space', + original_space: 'test-space', + custom_heartbeat_id: 'test-id-3-test-project-id-test-space', + timeout: null, + }, + unsupportedKeys: [], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts new file mode 100644 index 0000000000000..aae7031435c74 --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + BrowserFields, + ConfigKey, + DataStream, + FormMonitorType, + Locations, + PrivateLocation, + ProjectMonitor, +} from '../../../../common/runtime_types'; +import { getNormalizeCommonFields, getValueInSeconds } from './common_fields'; +import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; + +export interface NormalizedProjectProps { + locations: Locations; + privateLocations: PrivateLocation[]; + monitor: ProjectMonitor; + projectId: string; + namespace: string; +} + +export const getNormalizeBrowserFields = ({ + locations = [], + privateLocations = [], + monitor, + projectId, + namespace, +}: NormalizedProjectProps): { normalizedFields: BrowserFields; unsupportedKeys: string[] } => { + const defaultFields = DEFAULT_FIELDS[DataStream.BROWSER]; + + const commonFields = getNormalizeCommonFields({ + locations, + privateLocations, + monitor, + projectId, + namespace, + }); + + const normalizedFields = { + ...commonFields, + [ConfigKey.MONITOR_TYPE]: DataStream.BROWSER, + [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP, + [ConfigKey.SOURCE_PROJECT_CONTENT]: + monitor.content || defaultFields[ConfigKey.SOURCE_PROJECT_CONTENT], + [ConfigKey.THROTTLING_CONFIG]: monitor.throttling + ? `${monitor.throttling.download}d/${monitor.throttling.upload}u/${monitor.throttling.latency}l` + : defaultFields[ConfigKey.THROTTLING_CONFIG], + [ConfigKey.DOWNLOAD_SPEED]: `${ + monitor.throttling?.download || defaultFields[ConfigKey.DOWNLOAD_SPEED] + }`, + [ConfigKey.UPLOAD_SPEED]: `${ + monitor.throttling?.upload || defaultFields[ConfigKey.UPLOAD_SPEED] + }`, + [ConfigKey.IS_THROTTLING_ENABLED]: + Boolean(monitor.throttling) || defaultFields[ConfigKey.IS_THROTTLING_ENABLED], + [ConfigKey.LATENCY]: `${monitor.throttling?.latency || defaultFields[ConfigKey.LATENCY]}`, + [ConfigKey.IGNORE_HTTPS_ERRORS]: + monitor.ignoreHTTPSErrors || defaultFields[ConfigKey.IGNORE_HTTPS_ERRORS], + [ConfigKey.SCREENSHOTS]: monitor.screenshot || defaultFields[ConfigKey.SCREENSHOTS], + [ConfigKey.PLAYWRIGHT_OPTIONS]: Object.keys(monitor.playwrightOptions || {}).length + ? JSON.stringify(monitor.playwrightOptions) + : defaultFields[ConfigKey.PLAYWRIGHT_OPTIONS], + [ConfigKey.PARAMS]: Object.keys(monitor.params || {}).length + ? JSON.stringify(monitor.params) + : defaultFields[ConfigKey.PARAMS], + [ConfigKey.JOURNEY_FILTERS_MATCH]: + monitor.filter?.match || defaultFields[ConfigKey.JOURNEY_FILTERS_MATCH], + [ConfigKey.TIMEOUT]: monitor.timeout + ? getValueInSeconds(monitor.timeout) + : defaultFields[ConfigKey.TIMEOUT], + }; + return { + normalizedFields: { + ...defaultFields, + ...normalizedFields, + }, + unsupportedKeys: [], + }; +}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts new file mode 100644 index 0000000000000..31aebd0e8586e --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { omit } from 'lodash'; +import { formatKibanaNamespace } from '../../../../common/formatters'; +import { + BrowserFields, + ConfigKey, + CommonFields, + DataStream, + PrivateLocation, + Locations, + ProjectMonitor, + ScheduleUnit, + SourceType, +} from '../../../../common/runtime_types'; +import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; +import { DEFAULT_COMMON_FIELDS } from '../../../../common/constants/monitor_defaults'; +import { NormalizedProjectProps } from '.'; + +export const getNormalizeCommonFields = ({ + locations = [], + privateLocations = [], + monitor, + projectId, + namespace, +}: NormalizedProjectProps): CommonFields => { + const defaultFields = DEFAULT_COMMON_FIELDS; + + const normalizedFields = { + [ConfigKey.JOURNEY_ID]: monitor.id || defaultFields[ConfigKey.JOURNEY_ID], + [ConfigKey.MONITOR_SOURCE_TYPE]: SourceType.PROJECT, + [ConfigKey.NAME]: monitor.name || '', + [ConfigKey.SCHEDULE]: { + number: `${monitor.schedule}`, + unit: ScheduleUnit.MINUTES, + }, + [ConfigKey.PROJECT_ID]: projectId, + [ConfigKey.LOCATIONS]: getMonitorLocations({ + monitor, + privateLocations, + publicLocations: locations, + }), + [ConfigKey.APM_SERVICE_NAME]: + monitor.apmServiceName || defaultFields[ConfigKey.APM_SERVICE_NAME], + [ConfigKey.TAGS]: getOptionalListField(monitor.tags) || defaultFields[ConfigKey.TAGS], + [ConfigKey.NAMESPACE]: formatKibanaNamespace(namespace) || defaultFields[ConfigKey.NAMESPACE], + [ConfigKey.ORIGINAL_SPACE]: namespace || defaultFields[ConfigKey.NAMESPACE], + [ConfigKey.CUSTOM_HEARTBEAT_ID]: getCustomHeartbeatId(monitor, projectId, namespace), + [ConfigKey.ENABLED]: monitor.enabled ?? defaultFields[ConfigKey.ENABLED], + }; + return { + ...defaultFields, + ...normalizedFields, + }; +}; + +export const getCustomHeartbeatId = ( + monitor: NormalizedProjectProps['monitor'], + projectId: string, + namespace: string +) => { + return `${monitor.id}-${projectId}-${namespace}`; +}; + +export const getMonitorLocations = ({ + privateLocations, + publicLocations, + monitor, +}: { + monitor: ProjectMonitor; + privateLocations: PrivateLocation[]; + publicLocations: Locations; +}) => { + const publicLocs = + monitor.locations?.map((id) => { + return publicLocations.find((location) => location.id === id); + }) || []; + const privateLocs = + monitor.privateLocations?.map((locationName) => { + return privateLocations.find( + (location) => + location.label.toLowerCase() === locationName.toLowerCase() || + location.id.toLowerCase() === locationName.toLowerCase() + ); + }) || []; + + return [...publicLocs, ...privateLocs].filter( + (location) => location !== undefined + ) as BrowserFields[ConfigKey.LOCATIONS]; +}; + +export const getValueInSeconds = (value: string) => { + const keyMap = { + h: 60 * 60, + m: 60, + s: 1, + }; + const key = value.slice(-1) as 'h' | 'm' | 's'; + const time = parseInt(value.slice(0, -1), 10); + const valueInSeconds = time * (keyMap[key] || 1); + return typeof valueInSeconds === 'number' ? `${valueInSeconds}` : null; +}; + +/** + * Accounts for array values that are optionally defined as a comma seperated list + * + * @param {Array | string} [value] + * @returns {array} Returns an array + */ +export const getOptionalListField = (value?: string[] | string): string[] => { + if (Array.isArray(value)) { + return value; + } + return value ? value.split(',') : []; +}; + +/** + * Accounts for heartbeat fields that are optionally an array or single string + * + * @param {Array | string} [value] + * @returns {string} Returns first item when the value is an array, or the value itself + */ +export const getOptionalArrayField = (value: string[] | string = '') => { + const array = getOptionalListField(value); + return array[0]; +}; + +/** + * Flattens arbitrary yaml into a synthetics monitor compatible configuration + * + * @param {Object} [monitor] + * @returns {Object} Returns an object containing synthetics-compatible configuration keys + */ +const flattenAndFormatObject = (obj: Record, prefix = '', keys: string[]) => + Object.keys(obj).reduce>((acc, k) => { + const pre = prefix.length ? prefix + '.' : ''; + const key = pre + k; + + /* If the key is an array of numbers, convert to an array of strings */ + if (Array.isArray(obj[k])) { + acc[key] = (obj[k] as unknown[]).map((value) => + typeof value === 'number' ? String(value) : value + ); + return acc; + } + + /* if the key is a supported key stop flattening early */ + if (keys.includes(key)) { + acc[key] = obj[k]; + return acc; + } + + if (typeof obj[k] === 'object') { + Object.assign(acc, flattenAndFormatObject(obj[k] as Record, pre + k, keys)); + } else { + acc[key] = obj[k]; + } + return acc; + }, {}); + +export const normalizeYamlConfig = (monitor: NormalizedProjectProps['monitor']) => { + const defaultFields = DEFAULT_FIELDS[monitor.type as DataStream]; + const supportedKeys = Object.keys(defaultFields); + const flattenedConfig = flattenAndFormatObject(monitor, '', supportedKeys); + const { + locations: _locations, + privateLocations: _privateLocations, + content: _content, + id: _id, + ...yamlConfig + } = flattenedConfig; + const unsupportedKeys = Object.keys(yamlConfig).filter((key) => !supportedKeys.includes(key)); + const supportedYamlConfig = omit(yamlConfig, unsupportedKeys); + + return { + yamlConfig: supportedYamlConfig, + unsupportedKeys, + }; +}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts new file mode 100644 index 0000000000000..6f637d818667a --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { getNormalizeCommonFields } from './common_fields'; +import { NormalizedProjectProps } from './browser_monitor'; +import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; +import { + ConfigKey, + DataStream, + FormMonitorType, + HTTPFields, +} from '../../../../common/runtime_types/monitor_management'; +import { normalizeYamlConfig, getValueInSeconds, getOptionalArrayField } from './common_fields'; + +export const getNormalizeHTTPFields = ({ + locations = [], + privateLocations = [], + monitor, + projectId, + namespace, +}: NormalizedProjectProps): { normalizedFields: HTTPFields; unsupportedKeys: string[] } => { + const defaultFields = DEFAULT_FIELDS[DataStream.HTTP]; + const { yamlConfig, unsupportedKeys } = normalizeYamlConfig(monitor); + + const commonFields = getNormalizeCommonFields({ + locations, + privateLocations, + monitor, + projectId, + namespace, + }); + + const normalizedFields = { + ...yamlConfig, + ...commonFields, + [ConfigKey.MONITOR_TYPE]: DataStream.HTTP, + [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.HTTP, + [ConfigKey.URLS]: getOptionalArrayField(monitor.urls) || defaultFields[ConfigKey.URLS], + [ConfigKey.MAX_REDIRECTS]: + monitor[ConfigKey.MAX_REDIRECTS] || defaultFields[ConfigKey.MAX_REDIRECTS], + [ConfigKey.TIMEOUT]: monitor.timeout + ? getValueInSeconds(monitor.timeout) + : defaultFields[ConfigKey.TIMEOUT], + }; + return { + normalizedFields: { + ...defaultFields, + ...normalizedFields, + }, + unsupportedKeys, + }; +}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.ts new file mode 100644 index 0000000000000..282475f94d7cd --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.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 { getNormalizeCommonFields } from './common_fields'; +import { NormalizedProjectProps } from './browser_monitor'; +import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; +import { + ConfigKey, + DataStream, + FormMonitorType, + ICMPFields, +} from '../../../../common/runtime_types/monitor_management'; +import { normalizeYamlConfig, getValueInSeconds, getOptionalArrayField } from './common_fields'; + +export const getNormalizeICMPFields = ({ + locations = [], + privateLocations = [], + monitor, + projectId, + namespace, +}: NormalizedProjectProps): { normalizedFields: ICMPFields; unsupportedKeys: string[] } => { + const defaultFields = DEFAULT_FIELDS[DataStream.ICMP]; + const { yamlConfig, unsupportedKeys } = normalizeYamlConfig(monitor); + + const commonFields = getNormalizeCommonFields({ + locations, + privateLocations, + monitor, + projectId, + namespace, + }); + + const normalizedFields = { + ...yamlConfig, + ...commonFields, + [ConfigKey.MONITOR_TYPE]: DataStream.ICMP, + [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.ICMP, + [ConfigKey.HOSTS]: + getOptionalArrayField(monitor[ConfigKey.HOSTS]) || defaultFields[ConfigKey.HOSTS], + [ConfigKey.TIMEOUT]: monitor.timeout + ? getValueInSeconds(monitor.timeout) + : defaultFields[ConfigKey.TIMEOUT], + [ConfigKey.WAIT]: monitor.wait + ? getValueInSeconds(monitor.wait) || defaultFields[ConfigKey.WAIT] + : defaultFields[ConfigKey.WAIT], + }; + return { + normalizedFields: { + ...defaultFields, + ...normalizedFields, + }, + unsupportedKeys, + }; +}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/index.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/index.ts new file mode 100644 index 0000000000000..82b2acfacbf5b --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/index.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + DataStream, + PrivateLocation, + Locations, + ProjectMonitor, +} from '../../../../common/runtime_types'; +import { getNormalizeBrowserFields } from './browser_monitor'; +import { getNormalizeICMPFields } from './icmp_monitor'; +import { getNormalizeTCPFields } from './tcp_monitor'; +import { getNormalizeHTTPFields } from './http_monitor'; + +export interface NormalizedProjectProps { + locations: Locations; + privateLocations: PrivateLocation[]; + monitor: ProjectMonitor; + projectId: string; + namespace: string; +} + +export const normalizeProjectMonitor = (props: NormalizedProjectProps) => { + const { monitor } = props; + const type = monitor.type || DataStream.BROWSER; + + switch (type) { + case DataStream.BROWSER: + return getNormalizeBrowserFields(props); + + case DataStream.HTTP: + return getNormalizeHTTPFields(props); + + case DataStream.TCP: + return getNormalizeTCPFields(props); + + case DataStream.ICMP: + return getNormalizeICMPFields(props); + default: + throw new Error(`Unsupported monitor type ${monitor.type}`); + } +}; + +export const normalizeProjectMonitors = ({ + locations = [], + privateLocations = [], + monitors = [], + projectId, + namespace, +}: { + locations: Locations; + privateLocations: PrivateLocation[]; + monitors: ProjectMonitor[]; + projectId: string; + namespace: string; +}) => { + return monitors.map((monitor) => { + return normalizeProjectMonitor({ monitor, locations, privateLocations, projectId, namespace }); + }); +}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.ts new file mode 100644 index 0000000000000..8a85b2959d804 --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.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 { NormalizedProjectProps } from './browser_monitor'; +import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; +import { normalizeYamlConfig, getValueInSeconds } from './common_fields'; + +import { + ConfigKey, + DataStream, + FormMonitorType, + TCPFields, +} from '../../../../common/runtime_types/monitor_management'; +import { getNormalizeCommonFields, getOptionalArrayField } from './common_fields'; + +export const getNormalizeTCPFields = ({ + locations = [], + privateLocations = [], + monitor, + projectId, + namespace, +}: NormalizedProjectProps): { normalizedFields: TCPFields; unsupportedKeys: string[] } => { + const defaultFields = DEFAULT_FIELDS[DataStream.TCP]; + const { yamlConfig, unsupportedKeys } = normalizeYamlConfig(monitor); + + const commonFields = getNormalizeCommonFields({ + locations, + privateLocations, + monitor, + projectId, + namespace, + }); + + const normalizedFields = { + ...yamlConfig, + ...commonFields, + [ConfigKey.MONITOR_TYPE]: DataStream.TCP, + [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.TCP, + [ConfigKey.HOSTS]: + getOptionalArrayField(monitor[ConfigKey.HOSTS]) || defaultFields[ConfigKey.HOSTS], + [ConfigKey.TIMEOUT]: monitor.timeout + ? getValueInSeconds(monitor.timeout) + : defaultFields[ConfigKey.TIMEOUT], + }; + return { + normalizedFields: { + ...defaultFields, + ...normalizedFields, + }, + unsupportedKeys, + }; +}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor_formatter.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts similarity index 97% rename from x-pack/plugins/synthetics/server/synthetics_service/project_monitor_formatter.test.ts rename to x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts index e7175bec58057..a5fb9b774cf2c 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor_formatter.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts @@ -10,20 +10,21 @@ import { INSUFFICIENT_FLEET_PERMISSIONS, ProjectMonitorFormatter, } from './project_monitor_formatter'; -import { LocationStatus } from '../../common/runtime_types'; +import { LocationStatus } from '../../../common/runtime_types'; import { times } from 'lodash'; -import { SyntheticsService } from './synthetics_service'; -import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; +import { SyntheticsService } from '../synthetics_service'; +import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; -import { SyntheticsMonitorClient } from './synthetics_monitor/synthetics_monitor_client'; +import { SyntheticsMonitorClient } from '../synthetics_monitor/synthetics_monitor_client'; import { httpServerMock } from '@kbn/core-http-server-mocks'; import { Subject } from 'rxjs'; -import { formatSecrets } from './utils'; +import { formatSecrets } from '../utils'; -import * as telemetryHooks from '../routes/telemetry/monitor_upgrade_sender'; +import * as telemetryHooks from '../../routes/telemetry/monitor_upgrade_sender'; const testMonitors = [ { + type: 'browser', throttling: { download: 5, upload: 3, latency: 20 }, schedule: 3, locations: [], @@ -46,6 +47,7 @@ const testMonitors = [ filter: { match: 'check if title is present 10 0' }, }, { + type: 'browser', throttling: { download: 5, upload: 3, latency: 20 }, schedule: 3, locations: [], diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts new file mode 100644 index 0000000000000..aa0be87f0e818 --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts @@ -0,0 +1,540 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { Subject } from 'rxjs'; +import { isEqual } from 'lodash'; +import { KibanaRequest } from '@kbn/core/server'; +import { + SavedObjectsUpdateResponse, + SavedObjectsClientContract, + SavedObjectsFindResult, +} from '@kbn/core/server'; +import pMap from 'p-map'; +import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; +import { syncNewMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/add_monitor_bulk'; +import { deleteMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/delete_monitor_bulk'; +import { SyntheticsMonitorClient } from '../synthetics_monitor/synthetics_monitor_client'; +import { syncEditedMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/edit_monitor_bulk'; +import { + BrowserFields, + ConfigKey, + SyntheticsMonitorWithSecrets, + EncryptedSyntheticsMonitor, + ServiceLocationErrors, + ProjectMonitor, + Locations, + SyntheticsMonitor, + MonitorFields, + PrivateLocation, +} from '../../../common/runtime_types'; +import { + syntheticsMonitorType, + syntheticsMonitor, +} from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; +import type { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; +import { formatSecrets, normalizeSecrets } from '../utils/secrets'; +import { + validateProjectMonitor, + validateMonitor, + ValidationResult, +} from '../../routes/monitor_cruds/monitor_validation'; +import { normalizeProjectMonitor } from './normalizers'; + +interface StaleMonitor { + stale: boolean; + journeyId: string; + savedObjectId: string; +} +type StaleMonitorMap = Record; +type FailedError = Array<{ id?: string; reason: string; details: string; payload?: object }>; + +export const INSUFFICIENT_FLEET_PERMISSIONS = + 'Insufficient permissions. In order to configure private locations, you must have Fleet and Integrations write permissions. To resolve, please generate a new API key with a user who has Fleet and Integrations write permissions.'; + +export class ProjectMonitorFormatter { + private projectId: string; + private spaceId: string; + private keepStale: boolean; + private locations: Locations; + private privateLocations: PrivateLocation[]; + private savedObjectsClient: SavedObjectsClientContract; + private encryptedSavedObjectsClient: EncryptedSavedObjectsClient; + private staleMonitorsMap: StaleMonitorMap = {}; + private monitors: ProjectMonitor[] = []; + public createdMonitors: string[] = []; + public deletedMonitors: string[] = []; + public updatedMonitors: string[] = []; + public staleMonitors: string[] = []; + public failedMonitors: FailedError = []; + public failedStaleMonitors: FailedError = []; + private server: UptimeServerSetup; + private projectFilter: string; + private syntheticsMonitorClient: SyntheticsMonitorClient; + private request: KibanaRequest; + private subject?: Subject; + + private writeIntegrationPoliciesPermissions?: boolean; + + constructor({ + locations, + privateLocations, + keepStale, + savedObjectsClient, + encryptedSavedObjectsClient, + projectId, + spaceId, + monitors, + server, + syntheticsMonitorClient, + request, + subject, + }: { + locations: Locations; + privateLocations: PrivateLocation[]; + keepStale: boolean; + savedObjectsClient: SavedObjectsClientContract; + encryptedSavedObjectsClient: EncryptedSavedObjectsClient; + projectId: string; + spaceId: string; + monitors: ProjectMonitor[]; + server: UptimeServerSetup; + syntheticsMonitorClient: SyntheticsMonitorClient; + request: KibanaRequest; + subject?: Subject; + }) { + this.projectId = projectId; + this.spaceId = spaceId; + this.locations = locations; + this.privateLocations = privateLocations; + this.keepStale = keepStale; + this.savedObjectsClient = savedObjectsClient; + this.encryptedSavedObjectsClient = encryptedSavedObjectsClient; + this.syntheticsMonitorClient = syntheticsMonitorClient; + this.monitors = monitors; + this.server = server; + this.projectFilter = `${syntheticsMonitorType}.attributes.${ConfigKey.PROJECT_ID}: "${this.projectId}"`; + this.request = request; + this.subject = subject; + } + + public configureAllProjectMonitors = async () => { + const existingMonitors = await this.getProjectMonitorsForProject(); + this.staleMonitorsMap = await this.getStaleMonitorsMap(existingMonitors); + + const normalizedNewMonitors: BrowserFields[] = []; + const normalizedUpdateMonitors: Array<{ + previousMonitor: SavedObjectsFindResult; + monitor: BrowserFields; + }> = []; + + for (const monitor of this.monitors) { + const previousMonitor = existingMonitors.find( + (monitorObj) => + (monitorObj.attributes as BrowserFields)[ConfigKey.JOURNEY_ID] === monitor.id + ); + + const normM = await this.validateProjectMonitor({ + monitor, + }); + if (normM) { + if (previousMonitor) { + this.updatedMonitors.push(monitor.id); + if (this.staleMonitorsMap[monitor.id]) { + this.staleMonitorsMap[monitor.id].stale = false; + } + normalizedUpdateMonitors.push({ monitor: normM as MonitorFields, previousMonitor }); + } else { + normalizedNewMonitors.push(normM as MonitorFields); + } + } + } + + await this.createMonitorsBulk(normalizedNewMonitors); + + const { updatedCount } = await this.updateMonitors(normalizedUpdateMonitors); + + if (normalizedUpdateMonitors.length > 0) { + let updateMessage = ''; + if (updatedCount > 0) { + updateMessage = `${updatedCount} monitor${ + updatedCount > 1 ? 's' : '' + } updated successfully.`; + } + + const noChanges = normalizedUpdateMonitors.length - updatedCount; + let noChangeMessage = ''; + if (noChanges > 0) { + noChangeMessage = `${noChanges} monitor${noChanges > 1 ? 's' : ''} found with no changes.`; + } + + this.handleStreamingMessage({ + message: `${updateMessage} ${noChangeMessage}`, + }); + } + + await this.handleStaleMonitors(); + }; + + validatePermissions = async ({ monitor }: { monitor: ProjectMonitor }) => { + if (this.writeIntegrationPoliciesPermissions || (monitor.privateLocations ?? []).length === 0) { + return; + } + const { + integrations: { writeIntegrationPolicies }, + } = await this.server.fleet.authz.fromRequest(this.request); + + this.writeIntegrationPoliciesPermissions = writeIntegrationPolicies; + + if (!writeIntegrationPolicies) { + throw new Error(INSUFFICIENT_FLEET_PERMISSIONS); + } + }; + + validateProjectMonitor = async ({ monitor }: { monitor: ProjectMonitor }) => { + try { + await this.validatePermissions({ monitor }); + + const { normalizedFields: normalizedMonitor, unsupportedKeys } = normalizeProjectMonitor({ + monitor, + locations: this.locations, + privateLocations: this.privateLocations, + projectId: this.projectId, + namespace: this.spaceId, + }); + + if (unsupportedKeys.length) { + this.failedMonitors.push({ + id: monitor.id, + reason: 'Unsupported Heartbeat option', + details: `The following Heartbeat options are not supported for ${ + monitor.type + } project monitors in ${this.server.kibanaVersion}: ${unsupportedKeys.join('|')}`, + }); + this.handleStreamingMessage({ + message: `${monitor.id}: failed to create or update monitor`, + }); + return null; + } + + /* Validates that the payload sent from the synthetics agent is valid */ + const { valid: isMonitorPayloadValid } = this.validateMonitor({ + validationResult: validateProjectMonitor({ + ...monitor, + type: normalizedMonitor[ConfigKey.MONITOR_TYPE], + }), + monitorId: monitor.id, + }); + + if (!isMonitorPayloadValid) { + return null; + } + + /* Validates that the normalized monitor is a valid monitor saved object type */ + const { valid: isNormalizedMonitorValid } = this.validateMonitor({ + validationResult: validateMonitor(normalizedMonitor as MonitorFields), + monitorId: monitor.id, + }); + + if (!isNormalizedMonitorValid) { + return null; + } + + return normalizedMonitor; + } catch (e) { + this.server.logger.error(e); + this.failedMonitors.push({ + id: monitor.id, + reason: 'Failed to create or update monitor', + details: e.message, + payload: monitor, + }); + this.handleStreamingMessage({ message: `${monitor.id}: failed to create or update monitor` }); + if (this.staleMonitorsMap[monitor.id]) { + this.staleMonitorsMap[monitor.id].stale = false; + } + } + }; + + private getStaleMonitorsMap = async ( + existingMonitors: Array> + ): Promise => { + const staleMonitors: StaleMonitorMap = {}; + + existingMonitors.forEach((savedObject) => { + const journeyId = (savedObject.attributes as BrowserFields)[ConfigKey.JOURNEY_ID]; + if (journeyId) { + staleMonitors[journeyId] = { + stale: true, + savedObjectId: savedObject.id, + journeyId, + }; + } + }); + + return staleMonitors; + }; + + public getProjectMonitorsForProject = async () => { + const finder = this.savedObjectsClient.createPointInTimeFinder({ + type: syntheticsMonitorType, + perPage: 1000, + filter: this.projectFilter, + }); + + const hits: Array> = []; + for await (const result of finder.find()) { + hits.push( + ...(result.saved_objects as Array>) + ); + } + + await finder.close(); + + return hits; + }; + + private createMonitorsBulk = async (monitors: BrowserFields[]) => { + try { + if (monitors.length > 0) { + const { newMonitors } = await syncNewMonitorBulk({ + normalizedMonitors: monitors, + server: this.server, + syntheticsMonitorClient: this.syntheticsMonitorClient, + soClient: this.savedObjectsClient, + request: this.request, + privateLocations: this.privateLocations, + spaceId: this.spaceId, + }); + + if (newMonitors && newMonitors.length === monitors.length) { + this.createdMonitors.push(...monitors.map((monitor) => monitor[ConfigKey.JOURNEY_ID]!)); + this.handleStreamingMessage({ + message: `${monitors.length} monitor${ + monitors.length > 1 ? 's' : '' + } created successfully.`, + }); + } else { + this.failedMonitors.push({ + reason: `Failed to create ${monitors.length} monitors`, + details: 'Failed to create monitors', + payload: monitors, + }); + this.handleStreamingMessage({ + message: `Failed to create ${monitors.length} monitors`, + }); + } + } + } catch (e) { + this.server.logger.error(e); + this.failedMonitors.push({ + reason: `Failed to create ${monitors.length} monitors`, + details: e.message, + payload: monitors, + }); + this.handleStreamingMessage({ + message: `Failed to create ${monitors.length} monitors`, + }); + } + }; + + private getDecryptedMonitors = async ( + monitors: Array> + ) => { + return await pMap( + monitors, + async (monitor) => + this.encryptedSavedObjectsClient.getDecryptedAsInternalUser( + syntheticsMonitor.name, + monitor.id, + { + namespace: monitor.namespaces?.[0], + } + ), + { concurrency: 500 } + ); + }; + + private updateMonitors = async ( + monitors: Array<{ + monitor: BrowserFields; + previousMonitor: SavedObjectsFindResult; + }> + ): Promise<{ + editedMonitors: Array>; + errors: ServiceLocationErrors; + updatedCount: number; + }> => { + const decryptedPreviousMonitors = await this.getDecryptedMonitors( + monitors.map((m) => m.previousMonitor) + ); + + const monitorsToUpdate = []; + + for (let i = 0; i < decryptedPreviousMonitors.length; i++) { + const decryptedPreviousMonitor = decryptedPreviousMonitors[i]; + const previousMonitor = monitors[i].previousMonitor; + const normalizedMonitor = monitors[i].monitor; + + const { + attributes: { [ConfigKey.REVISION]: _, ...normalizedPreviousMonitorAttributes }, + } = normalizeSecrets(decryptedPreviousMonitor); + const hasMonitorBeenEdited = !isEqual(normalizedMonitor, normalizedPreviousMonitorAttributes); + + if (hasMonitorBeenEdited) { + const monitorWithRevision = formatSecrets({ + ...normalizedPreviousMonitorAttributes, + ...normalizedMonitor, + revision: (previousMonitor.attributes[ConfigKey.REVISION] || 0) + 1, + }); + monitorsToUpdate.push({ + normalizedMonitor, + previousMonitor, + monitorWithRevision, + decryptedPreviousMonitor, + }); + } + } + + const { editedMonitors } = await syncEditedMonitorBulk({ + monitorsToUpdate, + server: this.server, + syntheticsMonitorClient: this.syntheticsMonitorClient, + savedObjectsClient: this.savedObjectsClient, + request: this.request, + privateLocations: this.privateLocations, + spaceId: this.spaceId, + }); + return { + editedMonitors: editedMonitors ?? [], + errors: [], + updatedCount: monitorsToUpdate.length, + }; + }; + + private handleStaleMonitors = async () => { + try { + const staleMonitorsList = Object.values(this.staleMonitorsMap).filter( + (monitor) => monitor.stale === true + ); + + const encryptedMonitors = await this.savedObjectsClient.bulkGet( + staleMonitorsList.map((staleMonitor) => ({ + id: staleMonitor.savedObjectId, + type: syntheticsMonitorType, + })) + ); + + let monitors = encryptedMonitors.saved_objects; + + const hasPrivateMonitor = monitors.some((monitor) => + monitor.attributes.locations.some((location) => !location.isServiceManaged) + ); + + if (hasPrivateMonitor) { + const { + integrations: { writeIntegrationPolicies }, + } = await this.server.fleet.authz.fromRequest(this.request); + if (!writeIntegrationPolicies) { + monitors = monitors.filter((monitor) => { + const hasPrivateLocation = monitor.attributes.locations.some( + (location) => !location.isServiceManaged + ); + if (hasPrivateLocation) { + const journeyId = (monitor.attributes as MonitorFields)[ConfigKey.JOURNEY_ID]!; + const monitorName = (monitor.attributes as MonitorFields)[ConfigKey.NAME]!; + this.handleStreamingMessage({ + message: `Monitor ${journeyId} could not be deleted`, + }); + this.failedStaleMonitors.push({ + id: journeyId, + reason: 'Failed to delete stale monitor', + details: `Unable to delete Synthetics package policy for monitor ${monitorName}. Fleet write permissions are needed to use Synthetics private locations.`, + }); + } + return !hasPrivateLocation; + }); + } + } + + const chunkSize = 100; + for (let i = 0; i < monitors.length; i += chunkSize) { + const chunkMonitors = monitors.slice(i, i + chunkSize); + try { + if (!this.keepStale) { + await deleteMonitorBulk({ + monitors: chunkMonitors, + savedObjectsClient: this.savedObjectsClient, + server: this.server, + syntheticsMonitorClient: this.syntheticsMonitorClient, + request: this.request, + }); + + for (const sm of chunkMonitors) { + const journeyId = (sm.attributes as MonitorFields)[ConfigKey.JOURNEY_ID]!; + + this.deletedMonitors.push(journeyId); + this.handleStreamingMessage({ + message: `Monitor ${journeyId} deleted successfully`, + }); + } + } else { + chunkMonitors.forEach((sm) => { + const journeyId = (sm.attributes as MonitorFields)[ConfigKey.JOURNEY_ID]!; + this.staleMonitors.push(journeyId); + }); + } + } catch (e) { + chunkMonitors.forEach((sm) => { + const journeyId = (sm.attributes as MonitorFields)[ConfigKey.JOURNEY_ID]!; + + this.handleStreamingMessage({ + message: `Monitor ${journeyId} could not be deleted`, + }); + this.failedStaleMonitors.push({ + id: journeyId, + reason: 'Failed to delete stale monitor', + details: e.message, + payload: staleMonitorsList.find( + (staleMonitor) => staleMonitor.savedObjectId === sm.id + ), + }); + }); + this.server.logger.error(e); + } + } + } catch (e) { + this.server.logger.error(e); + } + }; + + private handleStreamingMessage = ({ message }: { message: string }) => { + if (this.subject) { + this.subject?.next(message); + } + }; + + private validateMonitor = ({ + validationResult, + monitorId, + }: { + validationResult: ValidationResult; + monitorId: string; + }) => { + const { reason: message, details, payload: validationPayload, valid } = validationResult; + if (!valid) { + this.failedMonitors.push({ + id: monitorId, + reason: message, + details, + payload: validationPayload, + }); + if (this.staleMonitorsMap[monitorId]) { + this.staleMonitorsMap[monitorId].stale = false; + } + } + return validationResult; + }; +} diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor_formatter.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor_formatter.ts deleted file mode 100644 index d35edae0e65ba..0000000000000 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor_formatter.ts +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { Subject } from 'rxjs'; -import { isEqual } from 'lodash'; -import { KibanaRequest } from '@kbn/core/server'; -import { - SavedObjectsUpdateResponse, - SavedObjectsClientContract, - SavedObjectsFindResult, -} from '@kbn/core/server'; -import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; -import { deleteMonitor } from '../routes/monitor_cruds/delete_monitor'; -import { syncNewMonitorBulk } from '../routes/monitor_cruds/bulk_cruds/add_monitor_bulk'; -import { SyntheticsMonitorClient } from './synthetics_monitor/synthetics_monitor_client'; -import { - BrowserFields, - ConfigKey, - SyntheticsMonitorWithSecrets, - EncryptedSyntheticsMonitor, - ServiceLocationErrors, - ProjectBrowserMonitor, - Locations, - PrivateLocation, -} from '../../common/runtime_types'; -import { - syntheticsMonitorType, - syntheticsMonitor, -} from '../legacy_uptime/lib/saved_objects/synthetics_monitor'; -import { normalizeProjectMonitor } from './normalizers/browser'; -import { formatSecrets, normalizeSecrets } from './utils/secrets'; -import { syncEditedMonitor } from '../routes/monitor_cruds/edit_monitor'; -import { validateProjectMonitor } from '../routes/monitor_cruds/monitor_validation'; -import type { UptimeServerSetup } from '../legacy_uptime/lib/adapters/framework'; - -interface StaleMonitor { - stale: boolean; - journeyId: string; - savedObjectId: string; -} -type StaleMonitorMap = Record; -type FailedMonitors = Array<{ id?: string; reason: string; details: string; payload?: object }>; - -export const INSUFFICIENT_FLEET_PERMISSIONS = - 'Insufficient permissions. In order to configure private locations, you must have Fleet and Integrations write permissions. To resolve, please generate a new API key with a user who has Fleet and Integrations write permissions.'; - -export class ProjectMonitorFormatter { - private projectId: string; - private spaceId: string; - private keepStale: boolean; - private locations: Locations; - private privateLocations: PrivateLocation[]; - private savedObjectsClient: SavedObjectsClientContract; - private encryptedSavedObjectsClient: EncryptedSavedObjectsClient; - private staleMonitorsMap: StaleMonitorMap = {}; - private monitors: ProjectBrowserMonitor[] = []; - public createdMonitors: string[] = []; - public deletedMonitors: string[] = []; - public updatedMonitors: string[] = []; - public staleMonitors: string[] = []; - public failedMonitors: FailedMonitors = []; - public failedStaleMonitors: FailedMonitors = []; - private server: UptimeServerSetup; - private projectFilter: string; - private syntheticsMonitorClient: SyntheticsMonitorClient; - private request: KibanaRequest; - private subject?: Subject; - - private writeIntegrationPoliciesPermissions?: boolean; - - constructor({ - locations, - privateLocations, - keepStale, - savedObjectsClient, - encryptedSavedObjectsClient, - projectId, - spaceId, - monitors, - server, - syntheticsMonitorClient, - request, - subject, - }: { - locations: Locations; - privateLocations: PrivateLocation[]; - keepStale: boolean; - savedObjectsClient: SavedObjectsClientContract; - encryptedSavedObjectsClient: EncryptedSavedObjectsClient; - projectId: string; - spaceId: string; - monitors: ProjectBrowserMonitor[]; - server: UptimeServerSetup; - syntheticsMonitorClient: SyntheticsMonitorClient; - request: KibanaRequest; - subject?: Subject; - }) { - this.projectId = projectId; - this.spaceId = spaceId; - this.locations = locations; - this.privateLocations = privateLocations; - this.keepStale = keepStale; - this.savedObjectsClient = savedObjectsClient; - this.encryptedSavedObjectsClient = encryptedSavedObjectsClient; - this.syntheticsMonitorClient = syntheticsMonitorClient; - this.monitors = monitors; - this.server = server; - this.projectFilter = `${syntheticsMonitorType}.attributes.${ConfigKey.PROJECT_ID}: "${this.projectId}"`; - this.request = request; - this.subject = subject; - } - - public configureAllProjectMonitors = async () => { - const existingMonitors = await this.getProjectMonitorsForProject(); - - this.staleMonitorsMap = await this.getStaleMonitorsMap(existingMonitors); - - const normalizedNewMonitors: BrowserFields[] = []; - const normalizedUpdateMonitors: BrowserFields[] = []; - - for (const monitor of this.monitors) { - const previousMonitor = existingMonitors.find( - (monitorObj) => - (monitorObj.attributes as BrowserFields)[ConfigKey.JOURNEY_ID] === monitor.id - ); - - const normM = await this.validateProjectMonitor({ - monitor, - }); - if (normM) { - if (previousMonitor) { - this.updatedMonitors.push(monitor.id); - if (this.staleMonitorsMap[monitor.id]) { - this.staleMonitorsMap[monitor.id].stale = false; - } - normalizedUpdateMonitors.push(normM); - } else { - normalizedNewMonitors.push(normM); - } - } - } - - await this.createMonitorsBulk(normalizedNewMonitors); - - await this.updateMonitorBulk(normalizedUpdateMonitors); - - await this.handleStaleMonitors(); - }; - - validatePermissions = async ({ monitor }: { monitor: ProjectBrowserMonitor }) => { - if (this.writeIntegrationPoliciesPermissions || (monitor.privateLocations ?? []).length === 0) { - return; - } - const { - integrations: { writeIntegrationPolicies }, - } = await this.server.fleet.authz.fromRequest(this.request); - - this.writeIntegrationPoliciesPermissions = writeIntegrationPolicies; - - if (!writeIntegrationPolicies) { - throw new Error(INSUFFICIENT_FLEET_PERMISSIONS); - } - }; - - validateProjectMonitor = async ({ monitor }: { monitor: ProjectBrowserMonitor }) => { - try { - await this.validatePermissions({ monitor }); - - const normalizedMonitor = normalizeProjectMonitor({ - monitor, - locations: this.locations, - privateLocations: this.privateLocations, - projectId: this.projectId, - namespace: this.spaceId, - }); - - const validationResult = validateProjectMonitor(monitor, this.projectId); - - if (!validationResult.valid) { - const { reason: message, details, payload } = validationResult; - this.failedMonitors.push({ - id: monitor.id, - reason: message, - details, - payload, - }); - if (this.staleMonitorsMap[monitor.id]) { - this.staleMonitorsMap[monitor.id].stale = false; - } - return null; - } - - return normalizedMonitor; - } catch (e) { - this.server.logger.error(e); - this.failedMonitors.push({ - id: monitor.id, - reason: 'Failed to create or update monitor', - details: e.message, - payload: monitor, - }); - this.handleStreamingMessage({ message: `${monitor.id}: failed to create or update monitor` }); - if (this.staleMonitorsMap[monitor.id]) { - this.staleMonitorsMap[monitor.id].stale = false; - } - } - }; - - private getStaleMonitorsMap = async ( - existingMonitors: Array> - ): Promise => { - const staleMonitors: StaleMonitorMap = {}; - - existingMonitors.forEach((savedObject) => { - const journeyId = (savedObject.attributes as BrowserFields)[ConfigKey.JOURNEY_ID]; - if (journeyId) { - staleMonitors[journeyId] = { - stale: true, - savedObjectId: savedObject.id, - journeyId, - }; - } - }); - - return staleMonitors; - }; - - public getProjectMonitorsForProject = async () => { - const finder = this.savedObjectsClient.createPointInTimeFinder({ - type: syntheticsMonitorType, - perPage: 1000, - filter: this.projectFilter, - }); - - const hits: Array> = []; - for await (const result of finder.find()) { - hits.push( - ...(result.saved_objects as Array>) - ); - } - - await finder.close(); - - return hits; - }; - - private getExistingMonitor = async ( - journeyId: string - ): Promise> => { - const filter = `${this.projectFilter} AND ${syntheticsMonitorType}.attributes.${ConfigKey.JOURNEY_ID}: "${journeyId}"`; - const { saved_objects: savedObjects } = - await this.savedObjectsClient.find({ - type: syntheticsMonitorType, - perPage: 1, - filter, - }); - return savedObjects?.[0]; - }; - - private createMonitorsBulk = async (monitors: BrowserFields[]) => { - try { - if (monitors.length > 0) { - const { newMonitors } = await syncNewMonitorBulk({ - normalizedMonitors: monitors, - server: this.server, - syntheticsMonitorClient: this.syntheticsMonitorClient, - soClient: this.savedObjectsClient, - request: this.request, - privateLocations: this.privateLocations, - spaceId: this.spaceId, - }); - - if (newMonitors && newMonitors.length === monitors.length) { - this.createdMonitors.push(...monitors.map((monitor) => monitor[ConfigKey.JOURNEY_ID]!)); - this.handleStreamingMessage({ - message: `${monitors.length} monitor${ - monitors.length > 1 ? 's' : '' - } created successfully.`, - }); - } else { - this.failedMonitors.push({ - reason: `Failed to create ${monitors.length} monitors`, - details: 'Failed to create monitors', - payload: monitors, - }); - this.handleStreamingMessage({ - message: `Failed to create ${monitors.length} monitors`, - }); - } - } - } catch (e) { - this.server.logger.error(e); - this.failedMonitors.push({ - reason: `Failed to create ${monitors.length} monitors`, - details: e.message, - payload: monitors, - }); - this.handleStreamingMessage({ - message: `Failed to create ${monitors.length} monitors`, - }); - } - }; - - private updateMonitorBulk = async (monitors: BrowserFields[]) => { - try { - for (const monitor of monitors) { - const previousMonitor = await this.getExistingMonitor(monitor[ConfigKey.JOURNEY_ID]!); - await this.updateMonitor(previousMonitor, monitor); - } - - if (monitors.length > 0) { - this.handleStreamingMessage({ - message: `${monitors.length} monitor${ - monitors.length > 1 ? 's' : '' - } updated successfully.`, - }); - } - } catch (e) { - this.server.logger.error(e); - this.failedMonitors.push({ - reason: 'Failed to update monitors', - details: e.message, - payload: monitors, - }); - this.handleStreamingMessage({ - message: `Failed to update ${monitors.length} monitors`, - }); - } - }; - - private updateMonitor = async ( - previousMonitor: SavedObjectsFindResult, - normalizedMonitor: BrowserFields - ): Promise<{ - editedMonitor: SavedObjectsUpdateResponse; - errors: ServiceLocationErrors; - }> => { - const decryptedPreviousMonitor = - await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser( - syntheticsMonitor.name, - previousMonitor.id, - { - namespace: previousMonitor.namespaces?.[0], - } - ); - const { - attributes: { [ConfigKey.REVISION]: _, ...normalizedPreviousMonitorAttributes }, - } = normalizeSecrets(decryptedPreviousMonitor); - const hasMonitorBeenEdited = !isEqual(normalizedMonitor, normalizedPreviousMonitorAttributes); - - if (hasMonitorBeenEdited) { - const monitorWithRevision = formatSecrets({ - ...normalizedPreviousMonitorAttributes, - ...normalizedMonitor, - revision: (previousMonitor.attributes[ConfigKey.REVISION] || 0) + 1, - }); - - const { editedMonitor } = await syncEditedMonitor({ - normalizedMonitor, - monitorWithRevision, - previousMonitor, - decryptedPreviousMonitor, - server: this.server, - syntheticsMonitorClient: this.syntheticsMonitorClient, - savedObjectsClient: this.savedObjectsClient, - request: this.request, - spaceId: this.spaceId, - }); - return { editedMonitor, errors: [] }; - } - - return { errors: [], editedMonitor: decryptedPreviousMonitor }; - }; - - private handleStaleMonitors = async () => { - try { - const staleMonitorsData = Object.values(this.staleMonitorsMap).filter( - (monitor) => monitor.stale === true - ); - - for (const staleMonitor of staleMonitorsData) { - if (!this.keepStale) { - await this.deleteStaleMonitor({ - monitorId: staleMonitor.savedObjectId, - journeyId: staleMonitor.journeyId, - }); - } else { - this.staleMonitors.push(staleMonitor.journeyId); - return null; - } - } - } catch (e) { - this.server.logger.error(e); - } - }; - - private deleteStaleMonitor = async ({ - monitorId, - journeyId, - }: { - monitorId: string; - journeyId: string; - }) => { - try { - await deleteMonitor({ - savedObjectsClient: this.savedObjectsClient, - server: this.server, - monitorId, - syntheticsMonitorClient: this.syntheticsMonitorClient, - request: this.request, - }); - this.deletedMonitors.push(journeyId); - this.handleStreamingMessage({ message: `Monitor ${journeyId} deleted successfully` }); - } catch (e) { - this.handleStreamingMessage({ message: `Monitor ${journeyId} could not be deleted` }); - this.failedStaleMonitors.push({ - id: journeyId, - reason: 'Failed to delete stale monitor', - details: e.message, - }); - } - }; - - private handleStreamingMessage = async ({ message }: { message: string }) => { - if (this.subject) { - this.subject?.next(message); - } - }; -} diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts index 947224dfaa00c..21f0e68588c73 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts @@ -121,28 +121,39 @@ describe('SyntheticsMonitorClient', () => { const id = 'test-id-1'; const client = new SyntheticsMonitorClient(syntheticsService, serverMock); - client.privateLocationAPI.editMonitor = jest.fn(); - - await client.editMonitor(monitor, id, mockRequest, savedObjectsClientMock, 'test-space'); + client.privateLocationAPI.editMonitors = jest.fn(); + + await client.editMonitors( + [ + { + monitor, + id, + }, + ], + mockRequest, + savedObjectsClientMock, + privateLocations, + 'test-space' + ); expect(syntheticsService.editConfig).toHaveBeenCalledTimes(1); - expect(client.privateLocationAPI.editMonitor).toHaveBeenCalledTimes(1); + expect(client.privateLocationAPI.editMonitors).toHaveBeenCalledTimes(1); }); it('should delete a monitor', async () => { locations[1].isServiceManaged = false; const client = new SyntheticsMonitorClient(syntheticsService, serverMock); - client.privateLocationAPI.deleteMonitor = jest.fn(); + client.privateLocationAPI.deleteMonitors = jest.fn(); - await client.deleteMonitor( - monitor as unknown as SyntheticsMonitorWithId, + await client.deleteMonitors( + [monitor as unknown as SyntheticsMonitorWithId], mockRequest, savedObjectsClientMock, 'test-space' ); expect(syntheticsService.deleteConfigs).toHaveBeenCalledTimes(1); - expect(client.privateLocationAPI.deleteMonitor).toHaveBeenCalledTimes(1); + expect(client.privateLocationAPI.deleteMonitors).toHaveBeenCalledTimes(1); }); }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts index b28768dcc95ff..8af7fca704ab0 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; +import { KibanaRequest, SavedObject, SavedObjectsClientContract } from '@kbn/core/server'; import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; import { SyntheticsPrivateLocation } from '../private_location/synthetics_private_location'; import { SyntheticsService } from '../synthetics_service'; @@ -15,6 +15,7 @@ import { SyntheticsMonitorWithId, HeartbeatConfig, PrivateLocation, + EncryptedSyntheticsMonitor, } from '../../../common/runtime_types'; export class SyntheticsMonitorClient { @@ -76,38 +77,74 @@ export class SyntheticsMonitorClient { return { newPolicies, syncErrors }; } - async editMonitor( - editedMonitor: MonitorFields, - id: string, + async editMonitors( + monitors: Array<{ + monitor: MonitorFields; + id: string; + previousMonitor?: SavedObject; + }>, request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract, + allPrivateLocations: PrivateLocation[], spaceId: string ) { - const editedConfig = formatHeartbeatRequest({ - monitor: editedMonitor, - monitorId: id, - customHeartbeatId: (editedMonitor as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID], - }); - - const { publicLocations } = this.parseLocations(editedConfig); + const privateConfigs: HeartbeatConfig[] = []; + const publicConfigs: HeartbeatConfig[] = []; - await this.privateLocationAPI.editMonitor(editedConfig, request, savedObjectsClient, spaceId); + for (const editedMonitor of monitors) { + const editedConfig = formatHeartbeatRequest({ + monitor: editedMonitor.monitor, + monitorId: editedMonitor.id, + customHeartbeatId: (editedMonitor.monitor as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID], + }); + const { publicLocations, privateLocations } = this.parseLocations(editedConfig); + if (publicLocations.length > 0) { + publicConfigs.push(editedConfig); + } - if (publicLocations.length > 0) { - return await this.syntheticsService.editConfig(editedConfig); + if (privateLocations.length > 0 || this.hasPrivateLocations(editedMonitor.previousMonitor)) { + privateConfigs.push(editedConfig); + } } - await this.syntheticsService.editConfig(editedConfig); - } + await this.privateLocationAPI.editMonitors( + privateConfigs, + request, + savedObjectsClient, + allPrivateLocations, + spaceId + ); - async deleteMonitor( - monitor: SyntheticsMonitorWithId, + if (publicConfigs.length > 0) { + return await this.syntheticsService.editConfig(publicConfigs); + } + } + async deleteMonitors( + monitors: SyntheticsMonitorWithId[], request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract, spaceId: string ) { - await this.privateLocationAPI.deleteMonitor(monitor, request, savedObjectsClient, spaceId); - return await this.syntheticsService.deleteConfigs([monitor]); + const privateDeletePromise = this.privateLocationAPI.deleteMonitors( + monitors, + request, + savedObjectsClient, + spaceId + ); + + const publicDeletePromise = this.syntheticsService.deleteConfigs(monitors); + const [pubicResponse] = await Promise.all([publicDeletePromise, privateDeletePromise]); + + return pubicResponse; + } + + hasPrivateLocations(previousMonitor?: SavedObject) { + if (!previousMonitor) { + return false; + } + const { locations } = previousMonitor.attributes; + + return locations.some((loc) => !loc.isServiceManaged); } parseLocations(config: HeartbeatConfig) { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index 6c815e5a2cbdf..d0211800dc6c0 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -282,8 +282,10 @@ export class SyntheticsService { } } - async editConfig(monitorConfig: HeartbeatConfig) { - const monitors = this.formatConfigs([monitorConfig]); + async editConfig(monitorConfig: HeartbeatConfig | HeartbeatConfig[]) { + const monitors = this.formatConfigs( + Array.isArray(monitorConfig) ? monitorConfig : [monitorConfig] + ); this.apiKey = await this.getApiKey(); 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 10d28fae3853c..0c389000f6c80 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -265,6 +265,23 @@ "type": "long" } } + }, + "count_connector_types_by_action_run_outcome_per_day": { + "properties": { + "DYNAMIC_KEY": { + "properties": { + "success": { + "type": "long" + }, + "failure": { + "type": "long" + }, + "unknown": { + "type": "long" + } + } + } + } } } }, @@ -1693,6 +1710,19 @@ } } }, + "count_rules_by_execution_status_per_day": { + "properties": { + "success": { + "type": "long" + }, + "failure": { + "type": "long" + }, + "unknown": { + "type": "long" + } + } + }, "avg_execution_time_per_day": { "type": "long" }, @@ -4614,6 +4644,19 @@ "properties": { "all": { "properties": { + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "total": { "type": "long" }, @@ -4677,6 +4720,19 @@ }, "sec": { "properties": { + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "total": { "type": "long" }, @@ -4693,6 +4749,19 @@ }, "obs": { "properties": { + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "total": { "type": "long" }, @@ -4709,6 +4778,19 @@ }, "main": { "properties": { + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "total": { "type": "long" }, @@ -4888,6 +4970,91 @@ } } }, + "cloudExperiments": { + "properties": { + "initialized": { + "type": "boolean", + "_meta": { + "description": "Whether the A/B testing client is correctly initialized (identify has been called)" + } + }, + "flags": { + "properties": { + "DYNAMIC_KEY": { + "type": "keyword", + "_meta": { + "description": "Flags received by the client" + } + } + } + }, + "flagNames": { + "type": "array", + "items": { + "type": "keyword", + "_meta": { + "description": "Names of the flags received by the client" + } + } + } + } + }, + "cloud_security_posture": { + "properties": { + "indices": { + "properties": { + "findings": { + "properties": { + "doc_count": { + "type": "long" + }, + "deleted": { + "type": "long" + }, + "size_in_bytes": { + "type": "long" + }, + "last_doc_timestamp": { + "type": "date" + } + } + }, + "latest_findings": { + "properties": { + "doc_count": { + "type": "long" + }, + "deleted": { + "type": "long" + }, + "size_in_bytes": { + "type": "long" + }, + "last_doc_timestamp": { + "type": "date" + } + } + }, + "score": { + "properties": { + "doc_count": { + "type": "long" + }, + "deleted": { + "type": "long" + }, + "size_in_bytes": { + "type": "long" + }, + "last_doc_timestamp": { + "type": "date" + } + } + } + } + } + } + }, "discoverEnhanced": { "properties": { "exploreDataInChartActionEnabled": { diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts index ccd91253012b3..e52effa09ab3b 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts @@ -25,6 +25,11 @@ import { ENDING_BREADCRUMB, FIELD_BROWSER, FIELD_BROWSER_MODAL, + FIELD_SELECTOR_TOGGLE_BUTTON, + FIELD_SELECTOR_INPUT, + FIELD_SELECTOR_LIST, + INSPECTOR_BUTTON, + INSPECTOR_PANEL, } from '../screens/indicators'; import { login } from '../tasks/login'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; @@ -143,16 +148,13 @@ describe('Indicators', () => { it('should have the default selected field, then update when user selects', () => { const threatFeedName = 'threat.feed.name'; - cy.get(`${FIELD_SELECTOR}`).should('have.value', threatFeedName); + cy.get(`${FIELD_SELECTOR_INPUT}`).eq(0).should('have.text', threatFeedName); - const threatIndicatorIp: string = 'threat.indicator.ip'; + const timestamp: string = '@timestamp'; - cy.get(`${FIELD_SELECTOR}`) - .should('exist') - .select(threatIndicatorIp) - .should('have.value', threatIndicatorIp); + cy.get(`${FIELD_SELECTOR_TOGGLE_BUTTON}`).should('exist').click(); - cy.get(`${FIELD_SELECTOR}`).should('have.value', threatIndicatorIp); + cy.get(`${FIELD_SELECTOR_LIST}`).should('exist').contains(timestamp); }); }); @@ -171,4 +173,22 @@ describe('Indicators', () => { }); }); }); + + describe('Request inspector', () => { + before(() => { + cy.visit(THREAT_INTELLIGENCE); + + selectRange(); + }); + + describe('when inspector button is clicked', () => { + it('should render the inspector flyout', () => { + cy.get(INSPECTOR_BUTTON).last().click({ force: true }); + + cy.get(INSPECTOR_PANEL).should('be.visible'); + + cy.get(INSPECTOR_PANEL).contains('Index patterns'); + }); + }); + }); }); diff --git a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts index 4ceaa0c020e99..2bc1b704e8159 100644 --- a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts +++ b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts @@ -23,7 +23,7 @@ export const FLYOUT_TITLE = `[data-test-subj="tiIndicatorFlyoutTitle"]`; export const FLYOUT_TABS = `[data-test-subj="tiIndicatorFlyoutTabs"]`; -export const FLYOUT_TABLE = `[data-test-subj="tiFlyoutTableMemoryTable"]`; +export const FLYOUT_TABLE = `[data-test-subj="tiFlyoutTableTabRow"]`; export const FLYOUT_JSON = `[data-test-subj="tiFlyoutJsonCodeBlock"]`; @@ -41,6 +41,13 @@ export const INDICATOR_TYPE_CELL = '[data-gridcell-column-id="threat.indicator.t export const FIELD_SELECTOR = '[data-test-subj="tiIndicatorFieldSelectorDropdown"]'; +export const FIELD_SELECTOR_INPUT = '[data-test-subj="comboBoxInput"]'; + +export const FIELD_SELECTOR_TOGGLE_BUTTON = '[data-test-subj="comboBoxToggleListButton"]'; + +export const FIELD_SELECTOR_LIST = + '[data-test-subj="comboBoxOptionsList tiIndicatorFieldSelectorDropdown-optionsList"]'; + export const FIELD_BROWSER = `[data-test-subj="show-field-browser"]`; export const FIELD_BROWSER_MODAL = `[data-test-subj="fields-browser-container"]`; @@ -107,3 +114,6 @@ export const INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON = export const INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON = '[data-test-subj="tiIndicatorFlyoutInvestigateInTimelineButton"]'; +export const INSPECTOR_BUTTON = '[data-test-subj="tiIndicatorsGridInspect"]'; + +export const INSPECTOR_PANEL = '[data-test-subj="inspectorPanel"]'; diff --git a/x-pack/plugins/threat_intelligence/kibana.json b/x-pack/plugins/threat_intelligence/kibana.json index efd1e8bce761e..f55191e68a875 100644 --- a/x-pack/plugins/threat_intelligence/kibana.json +++ b/x-pack/plugins/threat_intelligence/kibana.json @@ -16,7 +16,8 @@ "kibanaUtils", "navigation", "kibanaReact", - "triggersActionsUi" + "triggersActionsUi", + "inspector" ], "requiredBundles": [ "data", diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx index d3a94bbcce96e..7e046e214b547 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx @@ -16,6 +16,7 @@ import { Storage } from '@kbn/kibana-utils-plugin/public'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { createTGridMocks } from '@kbn/timelines-plugin/public/mock'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { KibanaContext } from '../../hooks/use_kibana'; import { SecuritySolutionPluginContext } from '../../types'; import { getSecuritySolutionContextMock } from './mock_security_context'; @@ -25,6 +26,7 @@ import { IndicatorsFiltersContext } from '../../modules/indicators/context'; import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context'; import { FieldTypesContext } from '../../containers/field_types_provider'; import { generateFieldTypeMap } from './mock_field_type_map'; +import { InspectorContext } from '../../containers/inspector'; export const localStorageMock = (): IStorage => { let store: Record = {}; @@ -125,19 +127,21 @@ export const mockedServices = { }; export const TestProvidersComponent: FC = ({ children }) => ( - - - - - - - {children} - - - - - - + + + + + + + + {children} + + + + + + + ); export type MockedSearch = jest.Mocked; diff --git a/x-pack/test/performance/page_objects.ts b/x-pack/plugins/threat_intelligence/public/containers/inspector/index.tsx similarity index 89% rename from x-pack/test/performance/page_objects.ts rename to x-pack/plugins/threat_intelligence/public/containers/inspector/index.tsx index 6c273213bf4a1..ed70296138688 100644 --- a/x-pack/test/performance/page_objects.ts +++ b/x-pack/plugins/threat_intelligence/public/containers/inspector/index.tsx @@ -5,4 +5,4 @@ * 2.0. */ -export const pageObjects = {}; +export * from './inspector'; diff --git a/x-pack/plugins/threat_intelligence/public/containers/inspector/inspector.tsx b/x-pack/plugins/threat_intelligence/public/containers/inspector/inspector.tsx new file mode 100644 index 0000000000000..622fa2df24640 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/containers/inspector/inspector.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 { RequestAdapter } from '@kbn/inspector-plugin/common'; +import React, { createContext, FC, useMemo } from 'react'; + +export interface InspectorContextValue { + requests: RequestAdapter; +} + +export const InspectorContext = createContext(undefined); + +export const InspectorProvider: FC = ({ children }) => { + const inspectorAdapters = useMemo(() => ({ requests: new RequestAdapter() }), []); + + return ( + {children} + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/hooks/use_inspector.ts b/x-pack/plugins/threat_intelligence/public/hooks/use_inspector.ts new file mode 100644 index 0000000000000..25aa0e5ef5da9 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/hooks/use_inspector.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 { useCallback, useContext, useEffect, useState } from 'react'; +import { InspectorSession } from '@kbn/inspector-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from './use_kibana'; +import { InspectorContext } from '../containers/inspector'; + +const INSPECTOR_FLYOUT_TITLE = i18n.translate('xpack.threatIntelligence.inspectorFlyoutTitle', { + defaultMessage: 'Indicators search requests', +}); + +/** + * + * @returns Exposes the adapters used to analyze requests and a method to open the inspector + */ +export const useInspector = () => { + const { + services: { inspector }, + } = useKibana(); + + const inspectorAdapters = useContext(InspectorContext); + + if (!inspectorAdapters) { + throw new Error('Inspector Context is not available'); + } + + const [inspectorSession, setInspectorSession] = useState(undefined); + + const onOpenInspector = useCallback(() => { + const session = inspector.open(inspectorAdapters, { + title: INSPECTOR_FLYOUT_TITLE, + }); + setInspectorSession(session); + }, [inspectorAdapters, inspector]); + + useEffect(() => { + return () => { + if (inspectorSession) { + inspectorSession.close(); + } + }; + }, [inspectorSession]); + + return { onOpenInspector, inspectorAdapters }; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/__snapshots__/indicators_barchart_wrapper.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/__snapshots__/indicators_barchart_wrapper.test.tsx.snap index de4d6c198961f..bc2b71303138b 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/__snapshots__/indicators_barchart_wrapper.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_barchart_wrapper/__snapshots__/indicators_barchart_wrapper.test.tsx.snap @@ -21,43 +21,67 @@ Object { class="euiFlexItem euiFlexItem--flexGrowZero" >
-
- + Stack by +
- + class="euiComboBoxPill euiComboBoxPill--plainText" + > + threat.feed.name + +
+ +
+
+
+
+ +
@@ -102,43 +126,67 @@ Object { class="euiFlexItem euiFlexItem--flexGrowZero" >
-
- + Stack by +
- + class="euiComboBoxPill euiComboBoxPill--plainText" + > + threat.feed.name + +
+ +
+
+
+
+ +
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.tsx index 2adcc4ee5b9ee..22cada18b84d8 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { BrowserField } from '@kbn/triggers-actions-ui-plugin/public/application/sections/field_browser/types'; +import { BrowserField } from '@kbn/rule-registry-plugin/common'; import { VFC } from 'react'; import { useKibana } from '../../../../hooks/use_kibana'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/__snapshots__/indicators_field_selector.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/__snapshots__/indicators_field_selector.test.tsx.snap index 735a1c34718c0..9346a739b9dae 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/__snapshots__/indicators_field_selector.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/__snapshots__/indicators_field_selector.test.tsx.snap @@ -6,43 +6,67 @@ Object { "baseElement":
-
- + Stack by +
- + class="euiComboBoxPill euiComboBoxPill--plainText" + > + threat.feed.name + +
+ +
+
+
+
+ +
@@ -50,43 +74,67 @@ Object { , "container":
-
- + Stack by +
- + class="euiComboBoxPill euiComboBoxPill--plainText" + > + threat.feed.name + +
+ +
+
+
+
+ +
@@ -151,32 +199,67 @@ Object { "baseElement":
-
- +
+
+
+
+ +
@@ -184,32 +267,67 @@ Object { , "container":
-
- +
+
+
+
+ +
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/indicators_field_selector.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/indicators_field_selector.test.tsx index e3bd4129d4fc5..0d62bbcef6219 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/indicators_field_selector.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_selector/indicators_field_selector.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; -import { DROPDOWN_TEST_ID, IndicatorsFieldSelector } from './indicators_field_selector'; +import { IndicatorsFieldSelector } from './indicators_field_selector'; const mockIndexPattern: DataView = { fields: [ @@ -51,11 +51,5 @@ describe('', () => { ); expect(component).toMatchSnapshot(); - - const dropdownOptions: string = component.getByTestId(DROPDOWN_TEST_ID).innerHTML; - const optionsCount: number = (dropdownOptions.match(/