diff --git a/.buildkite/pipelines/pull_request/deploy_cloud.yml b/.buildkite/pipelines/pull_request/deploy_cloud.yml index 718591195323..5306b4f0094b 100644 --- a/.buildkite/pipelines/pull_request/deploy_cloud.yml +++ b/.buildkite/pipelines/pull_request/deploy_cloud.yml @@ -5,6 +5,7 @@ steps: queue: n2-2-spot depends_on: build timeout_in_minutes: 30 + soft_fail: true retry: automatic: - exit_status: '-1' diff --git a/.buildkite/scripts/common/setup_bazel.sh b/.buildkite/scripts/common/setup_bazel.sh index de381010b8aa..503ce2ef06c9 100755 --- a/.buildkite/scripts/common/setup_bazel.sh +++ b/.buildkite/scripts/common/setup_bazel.sh @@ -17,6 +17,12 @@ if [[ "$BAZEL_CACHE_MODE" == "gcs" ]]; then echo "[bazel] enabling caching with GCS buckets" BAZEL_REGION="${BUILDKITE_AGENT_GCP_REGION:-us-central1}" + + if ! [[ "$BAZEL_REGION" =~ ^(us-central1|northamerica-northeast2|europe-west2|southamerica-east1|asia-south2)$ ]]; then + echo "unsupported bazel cache region $BAZEL_REGION" + exit 1 + fi + BAZEL_BUCKET="kibana-ci-bazel_$BAZEL_REGION" echo "[bazel] using GCS bucket: $BAZEL_BUCKET" diff --git a/.buildkite/scripts/steps/artifacts/build.sh b/.buildkite/scripts/steps/artifacts/build.sh index 337d0289daa7..7c18dcb328a2 100644 --- a/.buildkite/scripts/steps/artifacts/build.sh +++ b/.buildkite/scripts/steps/artifacts/build.sh @@ -29,5 +29,6 @@ if [ -d .beats ]; then cd .beats buildkite-agent artifact upload 'metricbeat-*' buildkite-agent artifact upload 'filebeat-*' + buildkite-agent artifact upload 'beats_manifest.json' cd - fi diff --git a/.buildkite/scripts/steps/artifacts/cloud.sh b/.buildkite/scripts/steps/artifacts/cloud.sh index 4d2317ce0b6c..16f280f68c53 100644 --- a/.buildkite/scripts/steps/artifacts/cloud.sh +++ b/.buildkite/scripts/steps/artifacts/cloud.sh @@ -7,31 +7,26 @@ set -euo pipefail source "$(dirname "$0")/../../common/util.sh" source .buildkite/scripts/steps/artifacts/env.sh -echo "--- Build and publish Cloud image" +echo "--- Push docker image" mkdir -p target -download_artifact "kibana-$FULL_VERSION-linux-x86_64.tar.gz" ./target --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" +download_artifact "kibana-cloud-$FULL_VERSION-docker-image.tar.gz" ./target --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" +docker load < "target/kibana-cloud-$FULL_VERSION-docker-image.tar.gz" TAG="$FULL_VERSION-$GIT_COMMIT" +KIBANA_BASE_IMAGE="docker.elastic.co/kibana-ci/kibana-cloud:$FULL_VERSION" KIBANA_TEST_IMAGE="docker.elastic.co/kibana-ci/kibana-cloud:$TAG" +# docker.elastic.co/kibana-ci/kibana-cloud:$FULL_VERSION -> :$FULL_VERSION-$GIT_COMMIT +docker tag "$KIBANA_BASE_IMAGE" "$KIBANA_TEST_IMAGE" + echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co trap 'docker logout docker.elastic.co' EXIT if docker manifest inspect $KIBANA_TEST_IMAGE &> /dev/null; then - echo "Distribution already exists, skipping build" + echo "Cloud image already exists, skipping docker push" else - node scripts/build \ - --skip-initialize \ - --skip-generic-folders \ - --skip-platform-folders \ - --skip-archives \ - --docker-images \ - --docker-tag-qualifier="$GIT_COMMIT" \ - --docker-push \ - --skip-docker-ubi \ - --skip-docker-ubuntu \ - --skip-docker-contexts + docker image push "$KIBANA_TEST_IMAGE" fi docker logout docker.elastic.co diff --git a/.buildkite/scripts/steps/artifacts/publish.sh b/.buildkite/scripts/steps/artifacts/publish.sh index 395a8196780d..6b79841b7f83 100644 --- a/.buildkite/scripts/steps/artifacts/publish.sh +++ b/.buildkite/scripts/steps/artifacts/publish.sh @@ -58,6 +58,10 @@ if [[ "$BUILDKITE_BRANCH" == "$KIBANA_BASE_BRANCH" ]]; then export VAULT_ROLE_ID="$(retry 5 15 gcloud secrets versions access latest --secret=kibana-buildkite-vault-role-id)" export VAULT_SECRET_ID="$(retry 5 15 gcloud secrets versions access latest --secret=kibana-buildkite-vault-secret-id)" export VAULT_ADDR="https://secrets.elastic.co:8200" + + download_artifact beats_manifest.json /tmp --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" + export BEATS_MANIFEST_URL=$(jq -r .manifest_url /tmp/beats_manifest.json) + docker run --rm \ --name release-manager \ -e VAULT_ADDR \ @@ -72,6 +76,7 @@ if [[ "$BUILDKITE_BRANCH" == "$KIBANA_BASE_BRANCH" ]]; then --workflow "$WORKFLOW" \ --version "$BASE_VERSION" \ --qualifier "$VERSION_QUALIFIER" \ + --dependency "beats:$BEATS_MANIFEST_URL" \ --artifact-set main ARTIFACTS_SUBDOMAIN="artifacts-$WORKFLOW" diff --git a/.buildkite/scripts/steps/functional/apm_cypress.sh b/.buildkite/scripts/steps/functional/apm_cypress.sh index 77b26fafee92..04f9aee28042 100755 --- a/.buildkite/scripts/steps/functional/apm_cypress.sh +++ b/.buildkite/scripts/steps/functional/apm_cypress.sh @@ -4,6 +4,8 @@ set -euo pipefail source .buildkite/scripts/common/util.sh +APM_CYPRESS_RECORD_KEY="$(retry 5 5 vault read -field=CYPRESS_RECORD_KEY secret/kibana-issues/dev/apm-cypress-dashboard-record-key)" + .buildkite/scripts/bootstrap.sh .buildkite/scripts/download_build_artifacts.sh @@ -15,4 +17,6 @@ cd "$XPACK_DIR" checks-reporter-with-killswitch "APM Cypress Tests" \ node plugins/apm/scripts/test/e2e.js \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" + --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ + --record \ + --key "$APM_CYPRESS_RECORD_KEY" diff --git a/.buildkite/scripts/steps/lint.sh b/.buildkite/scripts/steps/lint.sh index 8c6a2e2e6202..301737c9132a 100755 --- a/.buildkite/scripts/steps/lint.sh +++ b/.buildkite/scripts/steps/lint.sh @@ -16,9 +16,9 @@ echo '--- Lint: eslint' # after possibly commiting fixed files to the repo set +e; if is_pr && ! is_auto_commit_disabled; then - git ls-files | grep -E '\.(js|mjs|ts|tsx)$' | xargs -n 250 -P 6 node scripts/eslint --no-cache --fix + git ls-files | grep -E '\.(js|mjs|ts|tsx)$' | xargs -n 250 -P 4 node scripts/eslint --no-cache --fix else - git ls-files | grep -E '\.(js|mjs|ts|tsx)$' | xargs -n 250 -P 6 node scripts/eslint --no-cache + git ls-files | grep -E '\.(js|mjs|ts|tsx)$' | xargs -n 250 -P 4 node scripts/eslint --no-cache fi eslint_exit=$? diff --git a/.eslintrc.js b/.eslintrc.js index 902643dbe506..0e43c15cca13 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1406,6 +1406,30 @@ module.exports = { ], }, }, + /** + * Allows snake_case variables in the server, because that's how we return API properties + */ + { + files: ['x-pack/plugins/enterprise_search/server/**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'variable', + modifiers: ['destructured'], + format: null, + leadingUnderscore: 'allow', + trailingUnderscore: 'allow', + }, + { + selector: 'variable', + format: ['camelCase', 'UPPER_CASE'], + leadingUnderscore: 'allow', + trailingUnderscore: 'allow', + }, + ], + }, + }, { // Source files only - allow `any` in test/mock files files: ['x-pack/plugins/enterprise_search/**/*.{ts,tsx}'], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 759a966ab3fe..17e8a0f1b4d4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -332,11 +332,15 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/event_log/ @elastic/response-ops /x-pack/plugins/task_manager/ @elastic/response-ops /x-pack/plugins/stack_connectors/ @elastic/response-ops -/x-pack/plugins/stack_connectors/public/connector_types/stack/ @elastic/response-ops-execution -/x-pack/plugins/stack_connectors/server/connector_types/stack/ @elastic/response-ops-execution -/x-pack/plugins/stack_connectors/public/connector_types/cases/ @elastic/response-ops-cases -/x-pack/plugins/stack_connectors/server/connector_types/cases/ @elastic/response-ops-cases +/x-pack/plugins/stack_connectors/public/connector_types/stack/ @elastic/response-ops @elastic/response-ops-execution +/x-pack/plugins/stack_connectors/server/connector_types/stack/ @elastic/response-ops @elastic/response-ops-execution +/x-pack/plugins/stack_connectors/public/connector_types/cases/ @elastic/response-ops @elastic/response-ops-cases +/x-pack/plugins/stack_connectors/server/connector_types/cases/ @elastic/response-ops @elastic/response-ops-cases /x-pack/test/alerting_api_integration/ @elastic/response-ops +/x-pack/test/alerting_api_integration/basic/tests/actions/connector_types/stack/ @elastic/response-ops @elastic/response-ops-execution +/x-pack/test/alerting_api_integration/basic/tests/actions/connector_types/cases/ @elastic/response-ops @elastic/response-ops-cases +/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/stack/ @elastic/response-ops @elastic/response-ops-execution +/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases/ @elastic/response-ops @elastic/response-ops-cases /x-pack/test/plugin_api_integration/test_suites/task_manager/ @elastic/response-ops /x-pack/plugins/triggers_actions_ui/ @elastic/response-ops /x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/response-ops @@ -504,6 +508,11 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/security_solution/server/lib/detection_engine/routes/fleet @elastic/security-detections-response-rules /x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rule_exceptions_route* @elastic/security-solution-platform +/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_exceptions_route* @elastic/security-solution-platform +/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route* @elastic/security-solution-platform +/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route* @elastic/security-detections-response-alerts +/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils @elastic/security-solution-platform /x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags @elastic/security-detections-response-rules /x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring @elastic/security-detections-response-rules /x-pack/plugins/security_solution/server/lib/detection_engine/rules @elastic/security-detections-response-rules @@ -824,6 +833,7 @@ packages/core/status/core-status-server-internal @elastic/kibana-core packages/core/status/core-status-server-mocks @elastic/kibana-core packages/core/test-helpers/core-test-helpers-deprecations-getters @elastic/kibana-core packages/core/test-helpers/core-test-helpers-http-setup-browser @elastic/kibana-core +packages/core/test-helpers/core-test-helpers-so-type-serializer @elastic/kibana-core packages/core/theme/core-theme-browser @elastic/kibana-core packages/core/theme/core-theme-browser-internal @elastic/kibana-core packages/core/theme/core-theme-browser-mocks @elastic/kibana-core diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index c39e6292c7e4..6e5cf72bf44a 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -3,6 +3,9 @@ on: branches: ['main'] types: ['labeled', 'closed'] +env: + NODE_ENV: kibana-github-action + jobs: backport: name: Backport PR @@ -16,7 +19,7 @@ jobs: ) steps: - name: Backport Action - uses: sqren/backport-github-action@v8.9.2 + uses: sqren/backport-github-action@v8.9.7 with: github_token: ${{secrets.KIBANAMACHINE_TOKEN}} diff --git a/api_docs/actions.devdocs.json b/api_docs/actions.devdocs.json index cefd0ffd4d6b..c20f6e15fb96 100644 --- a/api_docs/actions.devdocs.json +++ b/api_docs/actions.devdocs.json @@ -741,13 +741,9 @@ "label": "request", "description": [], "signature": [ - "({ url, data, method, responseSchema, headers, ...config }: { url: string; responseSchema: ", - "Type", - "; method?: ", - "Method", - " | undefined; } & ", - "AxiosRequestConfig", - ") => Promise<", + "({ url, data, method, responseSchema, headers, ...config }: ", + "SubActionRequestParams", + ") => Promise<", "AxiosResponse", ">" ], @@ -763,13 +759,8 @@ "label": "{\n url,\n data,\n method = 'get',\n responseSchema,\n headers,\n ...config\n }", "description": [], "signature": [ - "{ url: string; responseSchema: ", - "Type", - "; method?: ", - "Method", - " | undefined; } & ", - "AxiosRequestConfig", - "" + "SubActionRequestParams", + "" ], "path": "x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts", "deprecated": false, @@ -858,6 +849,41 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "actions", + "id": "def-server.urlAllowListValidator", + "type": "Function", + "tags": [], + "label": "urlAllowListValidator", + "description": [], + "signature": [ + "(urlKey: string) => (obj: T, validatorServices: ", + "ValidatorServices", + ") => void" + ], + "path": "x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "actions", + "id": "def-server.urlAllowListValidator.$1", + "type": "string", + "tags": [], + "label": "urlKey", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [ @@ -1191,7 +1217,13 @@ "label": "validate", "description": [], "signature": [ - "{ params?: ValidatorType | undefined; config?: ValidatorType | undefined; secrets?: ValidatorType | undefined; connector?: ((config: Config, secrets: Secrets) => string | null) | undefined; } | undefined" + "{ params?: ", + "ValidatorType", + " | undefined; config?: ", + "ValidatorType", + " | undefined; secrets?: ", + "ValidatorType", + " | undefined; connector?: ((config: Config, secrets: Secrets) => string | null) | undefined; } | undefined" ], "path": "x-pack/plugins/actions/server/types.ts", "deprecated": false, @@ -1205,59 +1237,12 @@ "label": "renderParameterTemplates", "description": [], "signature": [ - "((params: Params, variables: Record, actionId?: string | undefined) => Params) | undefined" + "RenderParameterTemplates", + " | undefined" ], "path": "x-pack/plugins/actions/server/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "actions", - "id": "def-server.ActionType.renderParameterTemplates.$1", - "type": "Uncategorized", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "Params" - ], - "path": "x-pack/plugins/actions/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionType.renderParameterTemplates.$2", - "type": "Object", - "tags": [], - "label": "variables", - "description": [], - "signature": [ - "Record" - ], - "path": "x-pack/plugins/actions/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionType.renderParameterTemplates.$3", - "type": "string", - "tags": [], - "label": "actionId", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/actions/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "actions", diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index f845dffebd44..2bab1ac914cb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.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 | |-------------------|-----------|------------------------|-----------------| -| 214 | 0 | 209 | 19 | +| 213 | 0 | 208 | 23 | ## Client diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index c624a6191fca..10139c992c0d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index bc33359fc9b0..f9ea9de800a2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index e0cc3c68239e..cf275b4f7fda 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -551,9 +551,9 @@ ", filterOpts: ", "AlertingAuthorizationFilterOpts", ") => Promise<{ filter?: ", - "KueryNode", - " | ", "JsonObject", + " | ", + "KueryNode", " | undefined; ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void; }>" ], "path": "x-pack/plugins/alerting/server/authorization/alerting_authorization.ts", @@ -634,9 +634,9 @@ "text": "WriteOperations" }, ") => Promise<{ filter?: ", - "KueryNode", - " | ", "JsonObject", + " | ", + "KueryNode", " | undefined; ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void; }>" ], "path": "x-pack/plugins/alerting/server/authorization/alerting_authorization.ts", @@ -2782,7 +2782,7 @@ "section": "def-common.RuleTypeParams", "text": "RuleTypeParams" }, - " = never>({ id, includeLegacyId, }: { id: string; includeLegacyId?: boolean | undefined; }) => Promise<", + " = never>({ id, includeLegacyId, includeSnoozeData, }: { id: string; includeLegacyId?: boolean | undefined; includeSnoozeData?: boolean | undefined; }) => Promise<", { "pluginId": "alerting", "scope": "common", diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index e2fad1a8907b..01684374e77b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index e3201e3df01c..ddbf9d8c9e22 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -2001,7 +2001,9 @@ "TypeC", "<{ dependencyName: ", "StringC", - "; }>]>; }>, ", + "; searchServiceDestinationMetrics: ", + "Type", + "; }>]>; }>, ", { "pluginId": "apm", "scope": "server", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index c5590b95953e..c30a0992fd7f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index fde29f929d3e..66bd7c7e641e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.devdocs.json b/api_docs/bfetch.devdocs.json index f060159bc0f3..32dbe24517f9 100644 --- a/api_docs/bfetch.devdocs.json +++ b/api_docs/bfetch.devdocs.json @@ -370,7 +370,9 @@ "IRouter", "<", "RequestHandlerContext", - "> | undefined) => void" + "> | undefined, options?: ", + "RouteConfigOptions", + "<\"get\" | \"post\" | \"put\" | \"delete\"> | undefined) => void" ], "path": "src/plugins/bfetch/server/plugin.ts", "deprecated": false, @@ -450,6 +452,22 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "bfetch", + "id": "def-server.BfetchServerSetup.addStreamingResponseRoute.$5", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "RouteConfigOptions", + "<\"get\" | \"post\" | \"put\" | \"delete\"> | undefined" + ], + "path": "src/plugins/bfetch/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index 1b66b97f34a9..e8065146fd3d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.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 | |-------------------|-----------|------------------------|-----------------| -| 80 | 1 | 71 | 2 | +| 81 | 1 | 72 | 2 | ## Client diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 72ab30c5fdb7..a98cfd8e0fd7 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-10-04 +date: 2022-10-10 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 2a80740fc995..99dd1bbe8935 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-10-04 +date: 2022-10-10 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 18c757fc0925..0d6e5deb4ebb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.devdocs.json b/api_docs/cloud.devdocs.json index 97aac894f3a0..89134783e726 100644 --- a/api_docs/cloud.devdocs.json +++ b/api_docs/cloud.devdocs.json @@ -2,27 +2,7 @@ "id": "cloud", "client": { "classes": [], - "functions": [ - { - "parentPluginId": "cloud", - "id": "def-public.Chat", - "type": "Function", - "tags": [], - "label": "Chat", - "description": [ - "\nA lazily-loaded component that will display a trigger that will allow the user to chat with a\nhuman operator when the service is enabled; otherwise, it renders nothing." - ], - "signature": [ - "() => JSX.Element" - ], - "path": "x-pack/plugins/cloud/public/components/index.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [], - "initialIsOpen": false - } - ], + "functions": [], "interfaces": [ { "parentPluginId": "cloud", @@ -132,22 +112,6 @@ "path": "x-pack/plugins/cloud/public/plugin.tsx", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "cloud", - "id": "def-public.CloudConfigType.chat", - "type": "Object", - "tags": [], - "label": "chat", - "description": [ - "Configuration to enable live chat in Cloud-enabled instances of Kibana." - ], - "signature": [ - "{ enabled: boolean; chatURL: string; }" - ], - "path": "x-pack/plugins/cloud/public/plugin.tsx", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -209,6 +173,83 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "cloud", + "id": "def-public.CloudStart.isCloudEnabled", + "type": "boolean", + "tags": [], + "label": "isCloudEnabled", + "description": [ + "\n`true` when Kibana is running on Elastic Cloud." + ], + "path": "x-pack/plugins/cloud/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cloud", + "id": "def-public.CloudStart.cloudId", + "type": "string", + "tags": [], + "label": "cloudId", + "description": [ + "\nCloud ID. Undefined if not running on Cloud." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/cloud/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cloud", + "id": "def-public.CloudStart.deploymentUrl", + "type": "string", + "tags": [], + "label": "deploymentUrl", + "description": [ + "\nThe full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/cloud/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cloud", + "id": "def-public.CloudStart.profileUrl", + "type": "string", + "tags": [], + "label": "profileUrl", + "description": [ + "\nThe full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/cloud/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "cloud", + "id": "def-public.CloudStart.organizationUrl", + "type": "string", + "tags": [], + "label": "organizationUrl", + "description": [ + "\nThe full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/cloud/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -336,6 +377,38 @@ "path": "x-pack/plugins/cloud/public/plugin.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "cloud", + "id": "def-public.CloudSetup.registerCloudService", + "type": "Function", + "tags": [], + "label": "registerCloudService", + "description": [], + "signature": [ + "(contextProvider: React.FC<{}>) => void" + ], + "path": "x-pack/plugins/cloud/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloud", + "id": "def-public.CloudSetup.registerCloudService.$1", + "type": "Function", + "tags": [], + "label": "contextProvider", + "description": [], + "signature": [ + "React.FC<{}>" + ], + "path": "x-pack/plugins/cloud/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "lifecycle": "setup", diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index dce65a128457..5ed1afbaec4d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; @@ -21,16 +21,13 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 29 | 0 | 24 | 0 | +| 34 | 0 | 26 | 0 | ## Client ### Setup -### Functions - - ### Interfaces diff --git a/api_docs/cloud_chat.devdocs.json b/api_docs/cloud_chat.devdocs.json new file mode 100644 index 000000000000..a5c0e8eb90cc --- /dev/null +++ b/api_docs/cloud_chat.devdocs.json @@ -0,0 +1,47 @@ +{ + "id": "cloudChat", + "client": { + "classes": [], + "functions": [ + { + "parentPluginId": "cloudChat", + "id": "def-public.Chat", + "type": "Function", + "tags": [], + "label": "Chat", + "description": [ + "\nA lazily-loaded component that will display a trigger that will allow the user to chat with a\nhuman operator when the service is enabled; otherwise, it renders nothing." + ], + "signature": [ + "() => JSX.Element" + ], + "path": "x-pack/plugins/cloud_integrations/cloud_chat/public/components/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx new file mode 100644 index 000000000000..154ea7db5849 --- /dev/null +++ b/api_docs/cloud_chat.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibCloudChatPluginApi +slug: /kibana-dev-docs/api/cloudChat +title: "cloudChat" +image: https://source.unsplash.com/400x175/?github +description: API docs for the cloudChat plugin +date: 2022-10-10 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] +--- +import cloudChatObj from './cloud_chat.devdocs.json'; + +Chat available on Elastic Cloud deployments for quicker assistance. + +Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1 | 0 | 0 | 0 | + +## Client + +### Functions + + diff --git a/api_docs/cloud_experiments.devdocs.json b/api_docs/cloud_experiments.devdocs.json index 0f827e35ec52..d08957de1664 100644 --- a/api_docs/cloud_experiments.devdocs.json +++ b/api_docs/cloud_experiments.devdocs.json @@ -94,119 +94,6 @@ ], "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", diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 653ae1b77ade..3f36edd65c5e 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/@elastic/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 16 | 0 | 0 | 0 | +| 12 | 0 | 0 | 0 | ## Common diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index b3b220266009..299993c715eb 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-10-04 +date: 2022-10-10 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 ac8e3d27f519..61acddd4e881 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-10-04 +date: 2022-10-10 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 79cb9a68aae5..fd7bbc2f1dad 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-10-04 +date: 2022-10-10 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 dc8c7cd70e37..197b48569567 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -907,8 +907,8 @@ "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.tsx" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" }, { "plugin": "telemetry", @@ -979,44 +979,44 @@ "path": "packages/core/status/core-status-server-internal/src/status_service.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { "plugin": "@kbn/core-analytics-browser-mocks", @@ -10930,14 +10930,6 @@ "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" @@ -14790,6 +14782,14 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/app/app.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts" + }, { "plugin": "@kbn/core-application-browser-internal", "path": "packages/core/application/core-application-browser-internal/src/ui/app_container.tsx" @@ -15374,7 +15374,11 @@ "SavedObjectsFindOptionsReference", " | ", "SavedObjectsFindOptionsReference", - "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; preference?: string | undefined; }" + "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", + "SavedObjectsFindOptionsReference", + " | ", + "SavedObjectsFindOptionsReference", + "[] | undefined; hasNoReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; preference?: string | undefined; }" ], "path": "node_modules/@types/kbn__core-saved-objects-api-browser/index.d.ts", "deprecated": false, @@ -19658,8 +19662,8 @@ "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.tsx" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" }, { "plugin": "telemetry", @@ -19730,44 +19734,44 @@ "path": "packages/core/status/core-status-server-internal/src/status_service.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.test.ts" + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" }, { "plugin": "@kbn/core-analytics-browser-mocks", @@ -21813,13 +21817,7 @@ "<", "RequestHandlerContext", "> & { resources: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.HttpResources", - "text": "HttpResources" - }, + "HttpResources", "; }" ], "path": "src/core/server/index.ts", @@ -25938,7 +25936,10 @@ "description": [ "\nHttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP.\nProvides API allowing plug-ins to respond with:\n- a pre-configured HTML page bootstrapping Kibana client app\n- custom HTML page\n- custom JS script file." ], - "path": "src/core/server/http_resources/types.ts", + "signature": [ + "HttpResources" + ], + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -25959,16 +25960,10 @@ ">(route: ", "RouteConfig", ", handler: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.HttpResourcesRequestHandler", - "text": "HttpResourcesRequestHandler" - }, + "HttpResourcesRequestHandler", ") => void" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -25983,7 +25978,7 @@ "RouteConfig", "" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -25996,16 +25991,10 @@ "label": "handler", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.HttpResourcesRequestHandler", - "text": "HttpResourcesRequestHandler" - }, + "HttpResourcesRequestHandler", "" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -26025,7 +26014,10 @@ "description": [ "\nAllows to configure HTTP response parameters" ], - "path": "src/core/server/http_resources/types.ts", + "signature": [ + "HttpResourcesRenderOptions" + ], + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -26042,7 +26034,7 @@ "ResponseHeaders", " | undefined" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -26058,7 +26050,10 @@ "description": [ "\nExtended set of {@link KibanaResponseFactory} helpers used to respond with HTML or JS resource." ], - "path": "src/core/server/http_resources/types.ts", + "signature": [ + "HttpResourcesServiceToolkit" + ], + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -26073,18 +26068,12 @@ ], "signature": [ "(options?: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.HttpResourcesRenderOptions", - "text": "HttpResourcesRenderOptions" - }, + "HttpResourcesRenderOptions", " | undefined) => Promise<", "IKibanaResponse", ">" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -26096,16 +26085,10 @@ "label": "options", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.HttpResourcesRenderOptions", - "text": "HttpResourcesRenderOptions" - }, + "HttpResourcesRenderOptions", " | undefined" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -26124,18 +26107,12 @@ ], "signature": [ "(options?: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.HttpResourcesRenderOptions", - "text": "HttpResourcesRenderOptions" - }, + "HttpResourcesRenderOptions", " | undefined) => Promise<", "IKibanaResponse", ">" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -26147,16 +26124,10 @@ "label": "options", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.HttpResourcesRenderOptions", - "text": "HttpResourcesRenderOptions" - }, + "HttpResourcesRenderOptions", " | undefined" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -26180,7 +26151,7 @@ "IKibanaResponse", "" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -26194,7 +26165,7 @@ "signature": [ "HttpResponseOptions" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -26218,7 +26189,7 @@ "IKibanaResponse", "" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -26232,7 +26203,7 @@ "signature": [ "HttpResponseOptions" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -36874,6 +36845,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-server.OpsMetrics.elasticsearch_client", + "type": "Object", + "tags": [], + "label": "elasticsearch_client", + "description": [ + "\nMetrics related to the elasticsearch client" + ], + "signature": [ + "ElasticsearchClientsMetrics" + ], + "path": "node_modules/@types/kbn__core-metrics-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "core", "id": "def-server.OpsMetrics.process", @@ -40582,14 +40569,6 @@ "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" @@ -44308,6 +44287,41 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsFindOptions.hasNoReference", + "type": "CompoundType", + "tags": [], + "label": "hasNoReference", + "description": [ + "\nSearch for documents *not* having a reference to the specified objects.\nUse `hasNoReferenceOperator` to specify the operator to use when searching for multiple references." + ], + "signature": [ + "SavedObjectsFindOptionsReference", + " | ", + "SavedObjectsFindOptionsReference", + "[] | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsFindOptions.hasNoReferenceOperator", + "type": "CompoundType", + "tags": [], + "label": "hasNoReferenceOperator", + "description": [ + "\nThe operator to use when searching by multiple references using the `hasNoReference` option. Defaults to `OR`" + ], + "signature": [ + "\"AND\" | \"OR\" | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "core", "id": "def-server.SavedObjectsFindOptions.defaultSearchOperator", @@ -51888,20 +51902,14 @@ "): ", "IKibanaResponse", "; } & ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.HttpResourcesServiceToolkit", - "text": "HttpResourcesServiceToolkit" - }, + "HttpResourcesServiceToolkit", ") => ", "IKibanaResponse", " | Promise<", "IKibanaResponse", ">" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "returnComment": [], @@ -51970,7 +51978,7 @@ "signature": [ "HttpResponseOptions" ], - "path": "src/core/server/http_resources/types.ts", + "path": "node_modules/@types/kbn__core-http-resources-server/index.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -53691,7 +53699,11 @@ "SavedObjectsFindOptionsReference", " | ", "SavedObjectsFindOptionsReference", - "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; typeToNamespacesMap?: Map | undefined; preference?: string | undefined; }" + "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", + "SavedObjectsFindOptionsReference", + " | ", + "SavedObjectsFindOptionsReference", + "[] | undefined; hasNoReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; typeToNamespacesMap?: Map | undefined; preference?: string | undefined; }" ], "path": "node_modules/@types/kbn__core-saved-objects-api-server/index.d.ts", "deprecated": false, @@ -53944,10 +53956,9 @@ "\n A user credentials container.\nIt accommodates the necessary auth credentials to impersonate the current user.\n" ], "signature": [ - "FakeRequest", - " | ", "KibanaRequest", - "" + " | ", + "FakeRequest" ], "path": "node_modules/@types/kbn__core-elasticsearch-server/index.d.ts", "deprecated": false, diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 48204394f0a8..eaf05e52d4d5 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-10-04 +date: 2022-10-10 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 | |-------------------|-----------|------------------------|-----------------| -| 2686 | 0 | 29 | 0 | +| 2689 | 0 | 23 | 0 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index dc07d3950324..2638867b78d5 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-10-04 +date: 2022-10-10 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 464789c8cd19..19197f8324fe 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-10-04 +date: 2022-10-10 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 5ad9b56b0057..d313c049a619 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-10-04 +date: 2022-10-10 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 10ca74caf4ce..33d5834e2191 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -2588,7 +2588,7 @@ "description": [], "signature": [ "PluginInitializerContext", - "; }>; sessions: Readonly<{} & { enabled: boolean; pageSize: number; trackingInterval: moment.Duration; cleanupInterval: moment.Duration; expireInterval: moment.Duration; monitoringTaskTimeout: moment.Duration; notTouchedTimeout: moment.Duration; notTouchedInProgressTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>>" + "; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>>" ], "path": "src/plugins/data/public/plugin.ts", "deprecated": false, @@ -7523,6 +7523,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-public.IKibanaSearchResponse.isStored", + "type": "CompoundType", + "tags": [], + "label": "isStored", + "description": [ + "\nIndicates whether the search has been saved to a search-session object and long keepAlive was set" + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", "id": "def-public.IKibanaSearchResponse.warning", @@ -7649,6 +7665,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-public.ISearchOptions.isSearchStored", + "type": "CompoundType", + "tags": [], + "label": "isSearchStored", + "description": [ + "\nWhether the search was successfully polled after session was saved. Search was added to a session saved object and keepAlive extended." + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", "id": "def-public.ISearchOptions.isRestore", @@ -11748,7 +11780,7 @@ "section": "def-server.PluginInitializerContext", "text": "PluginInitializerContext" }, - "; }>; sessions: Readonly<{} & { enabled: boolean; pageSize: number; trackingInterval: moment.Duration; cleanupInterval: moment.Duration; expireInterval: moment.Duration; monitoringTaskTimeout: moment.Duration; notTouchedTimeout: moment.Duration; notTouchedInProgressTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>>" + "; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>>" ], "path": "src/plugins/data/server/plugin.ts", "deprecated": false, @@ -17029,6 +17061,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-server.ISearchOptions.isSearchStored", + "type": "CompoundType", + "tags": [], + "label": "isSearchStored", + "description": [ + "\nWhether the search was successfully polled after session was saved. Search was added to a session saved object and keepAlive extended." + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", "id": "def-server.ISearchOptions.isRestore", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index f88ba52d6af1..705e1202dd7a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.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 | |-------------------|-----------|------------------------|-----------------| -| 3213 | 33 | 2509 | 23 | +| 3221 | 33 | 2513 | 24 | ## Client diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 554f11d22eb3..4f27bd7c2645 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.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 | |-------------------|-----------|------------------------|-----------------| -| 3213 | 33 | 2509 | 23 | +| 3221 | 33 | 2513 | 24 | ## Client diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index 3be213361786..08d6314c52ff 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -208,7 +208,9 @@ "Observable", "<", "SessionMeta", - ">; hasAccess: () => boolean; trackSearch: (searchDescriptor: TrackSearchDescriptor) => () => void; getSessionId: () => string | undefined; getSession$: () => ", + ">; readonly disableSaveAfterSearchesExpire$: ", + "Observable", + "; hasAccess: () => boolean; trackSearch: (searchDescriptor: TrackSearchDescriptor) => TrackSearchHandler; getSessionId: () => string | undefined; getSession$: () => ", "Observable", "; isStored: () => boolean; isRestore: () => boolean; restore: (sessionId: string) => void; continue: (sessionId: string) => void; clear: () => void; cancel: () => Promise; renameCurrentSession: (newName: string) => Promise; isCurrentSession: (sessionId?: string | undefined) => boolean; getSearchOptions: (sessionId?: string | undefined) => Required; find: (options: Omit<", "SavedObjectsFindOptions", ", \"type\">) => Promise<", - "SavedObjectsFindResponse", - ">; update: (sessionId: string, attributes: unknown) => Promise<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionsFindResponse", + "text": "SearchSessionsFindResponse" + }, + ">; update: (sessionId: string, attributes: unknown) => Promise<", "SavedObjectsUpdateResponse", "<", { @@ -638,7 +646,9 @@ "Observable", "<", "SessionMeta", - ">; hasAccess: () => boolean; trackSearch: (searchDescriptor: TrackSearchDescriptor) => () => void; getSessionId: () => string | undefined; getSession$: () => ", + ">; readonly disableSaveAfterSearchesExpire$: ", + "Observable", + "; hasAccess: () => boolean; trackSearch: (searchDescriptor: TrackSearchDescriptor) => TrackSearchHandler; getSessionId: () => string | undefined; getSession$: () => ", "Observable", "; isStored: () => boolean; isRestore: () => boolean; restore: (sessionId: string) => void; continue: (sessionId: string) => void; clear: () => void; cancel: () => Promise; renameCurrentSession: (newName: string) => Promise; isCurrentSession: (sessionId?: string | undefined) => boolean; getSearchOptions: (sessionId?: string | undefined) => Required; find: (options: Omit<", "SavedObjectsFindOptions", ", \"type\">) => Promise<", - "SavedObjectsFindResponse", - ">; update: (sessionId: string, attributes: unknown) => Promise<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionsFindResponse", + "text": "SearchSessionsFindResponse" + }, + ">; update: (sessionId: string, attributes: unknown) => Promise<", "SavedObjectsUpdateResponse", "<", { @@ -1228,8 +1244,14 @@ ">; find: (options: Omit<", "SavedObjectsFindOptions", ", \"type\">) => Promise<", - "SavedObjectsFindResponse", - ">; update: (sessionId: string, attributes: unknown) => Promise<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionsFindResponse", + "text": "SearchSessionsFindResponse" + }, + ">; update: (sessionId: string, attributes: unknown) => Promise<", "SavedObjectsUpdateResponse", "<", { @@ -1288,7 +1310,9 @@ "Observable", "<", "SessionMeta", - ">; hasAccess: () => boolean; trackSearch: (searchDescriptor: TrackSearchDescriptor) => () => void; getSessionId: () => string | undefined; getSession$: () => ", + ">; readonly disableSaveAfterSearchesExpire$: ", + "Observable", + "; hasAccess: () => boolean; trackSearch: (searchDescriptor: TrackSearchDescriptor) => TrackSearchHandler; getSessionId: () => string | undefined; getSession$: () => ", "Observable", "; isStored: () => boolean; isRestore: () => boolean; restore: (sessionId: string) => void; continue: (sessionId: string) => void; clear: () => void; cancel: () => Promise; renameCurrentSession: (newName: string) => Promise; isCurrentSession: (sessionId?: string | undefined) => boolean; getSearchOptions: (sessionId?: string | undefined) => Required; }>; sessions: Readonly<{} & { enabled: boolean; pageSize: number; trackingInterval: moment.Duration; cleanupInterval: moment.Duration; expireInterval: moment.Duration; monitoringTaskTimeout: moment.Duration; notTouchedTimeout: moment.Duration; notTouchedInProgressTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>" + "Readonly<{} & { search: Readonly<{} & { aggs: Readonly<{} & { shardDelay: Readonly<{} & { enabled: boolean; }>; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>" ], "path": "src/plugins/data/server/search/session/session_service.ts", "deprecated": false, @@ -1564,7 +1588,7 @@ "section": "def-server.CoreStart", "text": "CoreStart" }, - ", deps: StartDependencies) => Promise" + ", deps: StartDependencies) => void" ], "path": "src/plugins/data/server/search/session/session_service.ts", "deprecated": false, @@ -1842,8 +1866,8 @@ "label": "find", "description": [], "signature": [ - "({ savedObjectsClient }: ", - "SearchSessionDependencies", + "({ savedObjectsClient, internalElasticsearchClient }: ", + "SearchSessionStatusDependencies", ", user: ", { "pluginId": "security", @@ -1855,16 +1879,14 @@ " | null, options: Omit<", "SavedObjectsFindOptions", ", \"type\">) => Promise<", - "SavedObjectsFindResponse", - "<", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSessionSavedObjectAttributes", - "text": "SearchSessionSavedObjectAttributes" + "section": "def-common.SearchSessionsFindResponse", + "text": "SearchSessionsFindResponse" }, - ", unknown>>" + ">" ], "path": "src/plugins/data/server/search/session/session_service.ts", "deprecated": false, @@ -1875,10 +1897,10 @@ "id": "def-server.SearchSessionService.find.$1", "type": "Object", "tags": [], - "label": "{ savedObjectsClient }", + "label": "{ savedObjectsClient, internalElasticsearchClient }", "description": [], "signature": [ - "SearchSessionDependencies" + "SearchSessionStatusDependencies" ], "path": "src/plugins/data/server/search/session/session_service.ts", "deprecated": false, @@ -2398,6 +2420,93 @@ ], "returnComment": [] }, + { + "parentPluginId": "data", + "id": "def-server.SearchSessionService.status", + "type": "Function", + "tags": [], + "label": "status", + "description": [], + "signature": [ + "(deps: ", + "SearchSessionStatusDependencies", + ", user: ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + " | null, sessionId: string) => Promise<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionStatusResponse", + "text": "SearchSessionStatusResponse" + }, + ">" + ], + "path": "src/plugins/data/server/search/session/session_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-server.SearchSessionService.status.$1", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "SearchSessionStatusDependencies" + ], + "path": "src/plugins/data/server/search/session/session_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "data", + "id": "def-server.SearchSessionService.status.$2", + "type": "CompoundType", + "tags": [], + "label": "user", + "description": [], + "signature": [ + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + " | null" + ], + "path": "src/plugins/data/server/search/session/session_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "data", + "id": "def-server.SearchSessionService.status.$3", + "type": "string", + "tags": [], + "label": "sessionId", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data/server/search/session/session_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-server.SearchSessionService.asScopedProvider", @@ -2406,7 +2515,7 @@ "label": "asScopedProvider", "description": [], "signature": [ - "({ savedObjects }: ", + "({ savedObjects, elasticsearch }: ", { "pluginId": "core", "scope": "server", @@ -2432,7 +2541,7 @@ "section": "def-common.ISearchOptions", "text": "ISearchOptions" }, - ") => Promise; trackId: (args_0: ", + ") => Promise; trackId: (searchRequest: ", { "pluginId": "data", "scope": "common", @@ -2440,7 +2549,7 @@ "section": "def-common.IKibanaSearchRequest", "text": "IKibanaSearchRequest" }, - ", args_1: string, args_2: ", + ", searchId: string, options: ", { "pluginId": "data", "scope": "common", @@ -2479,16 +2588,14 @@ ">>; find: (options: Omit<", "SavedObjectsFindOptions", ", \"type\">) => Promise<", - "SavedObjectsFindResponse", - "<", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSessionSavedObjectAttributes", - "text": "SearchSessionSavedObjectAttributes" + "section": "def-common.SearchSessionsFindResponse", + "text": "SearchSessionsFindResponse" }, - ", unknown>>; update: (sessionId: string, attributes: Partial<", + ">; update: (sessionId: string, attributes: Partial<", { "pluginId": "data", "scope": "common", @@ -2526,7 +2633,15 @@ "section": "def-common.SearchSessionSavedObjectAttributes", "text": "SearchSessionSavedObjectAttributes" }, - ">>; delete: (sessionId: string) => Promise<{}>; getConfig: () => Readonly<{} & { enabled: boolean; pageSize: number; trackingInterval: moment.Duration; cleanupInterval: moment.Duration; expireInterval: moment.Duration; monitoringTaskTimeout: moment.Duration; notTouchedTimeout: moment.Duration; notTouchedInProgressTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }" + ">>; delete: (sessionId: string) => Promise<{}>; status: (sessionId: string) => Promise<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionStatusResponse", + "text": "SearchSessionStatusResponse" + }, + ">; getConfig: () => Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }" ], "path": "src/plugins/data/server/search/session/session_service.ts", "deprecated": false, @@ -2537,7 +2652,7 @@ "id": "def-server.SearchSessionService.asScopedProvider.$1", "type": "Object", "tags": [], - "label": "{ savedObjects }", + "label": "{ savedObjects, elasticsearch }", "description": [], "signature": [ { @@ -2693,15 +2808,7 @@ "label": "attributes", "description": [], "signature": [ - "{ sessionId?: string | undefined; name?: string | undefined; appId?: string | undefined; created?: string | undefined; touched?: string | undefined; expires?: string | undefined; completed?: string | null | undefined; status?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSessionStatus", - "text": "SearchSessionStatus" - }, - " | undefined; locatorId?: string | undefined; initialState?: ", + "{ sessionId?: string | undefined; name?: string | undefined; appId?: string | undefined; created?: string | undefined; expires?: string | undefined; locatorId?: string | undefined; initialState?: ", "SerializableRecord", " | undefined; restoreState?: ", "SerializableRecord", @@ -2713,7 +2820,7 @@ "section": "def-common.SearchSessionRequestInfo", "text": "SearchSessionRequestInfo" }, - "> | undefined; persisted?: boolean | undefined; realmType?: string | undefined; realmName?: string | undefined; username?: string | undefined; version?: string | undefined; }" + "> | undefined; realmType?: string | undefined; realmName?: string | undefined; username?: string | undefined; version?: string | undefined; isCanceled?: boolean | undefined; }" ], "path": "src/plugins/data/server/search/session/types.ts", "deprecated": false, @@ -2770,16 +2877,14 @@ "(options: Omit<", "SavedObjectsFindOptions", ", \"type\">) => Promise<", - "SavedObjectsFindResponse", - "<", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSessionSavedObjectAttributes", - "text": "SearchSessionSavedObjectAttributes" + "section": "def-common.SearchSessionsFindResponse", + "text": "SearchSessionsFindResponse" }, - ", unknown>>" + ">" ], "path": "src/plugins/data/server/search/types.ts", "deprecated": false, @@ -2802,7 +2907,11 @@ "SavedObjectsFindOptionsReference", " | ", "SavedObjectsFindOptionsReference", - "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; typeToNamespacesMap?: Map | undefined; preference?: string | undefined; pit?: ", + "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", + "SavedObjectsFindOptionsReference", + " | ", + "SavedObjectsFindOptionsReference", + "[] | undefined; hasNoReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; typeToNamespacesMap?: Map | undefined; preference?: string | undefined; pit?: ", "SavedObjectsPitParams", " | undefined; }" ], @@ -2864,15 +2973,7 @@ "label": "attributes", "description": [], "signature": [ - "{ sessionId?: string | undefined; name?: string | undefined; appId?: string | undefined; created?: string | undefined; touched?: string | undefined; expires?: string | undefined; completed?: string | null | undefined; status?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSessionStatus", - "text": "SearchSessionStatus" - }, - " | undefined; locatorId?: string | undefined; initialState?: ", + "{ sessionId?: string | undefined; name?: string | undefined; appId?: string | undefined; created?: string | undefined; expires?: string | undefined; locatorId?: string | undefined; initialState?: ", "SerializableRecord", " | undefined; restoreState?: ", "SerializableRecord", @@ -2884,7 +2985,7 @@ "section": "def-common.SearchSessionRequestInfo", "text": "SearchSessionRequestInfo" }, - "> | undefined; persisted?: boolean | undefined; realmType?: string | undefined; realmName?: string | undefined; username?: string | undefined; version?: string | undefined; }" + "> | undefined; realmType?: string | undefined; realmName?: string | undefined; username?: string | undefined; version?: string | undefined; isCanceled?: boolean | undefined; }" ], "path": "src/plugins/data/server/search/session/types.ts", "deprecated": false, @@ -2999,6 +3100,42 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "data", + "id": "def-server.IScopedSearchClient.getSessionStatus", + "type": "Function", + "tags": [], + "label": "getSessionStatus", + "description": [], + "signature": [ + "(sessionId: string) => Promise<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionStatusResponse", + "text": "SearchSessionStatusResponse" + }, + ">" + ], + "path": "src/plugins/data/server/search/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "data", + "id": "def-server.IScopedSearchClient.getSessionStatus.$1", + "type": "string", + "tags": [], + "label": "sessionId", + "description": [], + "path": "src/plugins/data/server/search/session/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "initialIsOpen": false @@ -26585,6 +26722,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-common.IKibanaSearchResponse.isStored", + "type": "CompoundType", + "tags": [], + "label": "isStored", + "description": [ + "\nIndicates whether the search has been saved to a search-session object and long keepAlive was set" + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", "id": "def-common.IKibanaSearchResponse.warning", @@ -27044,6 +27197,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-common.ISearchOptions.isSearchStored", + "type": "CompoundType", + "tags": [], + "label": "isSearchStored", + "description": [ + "\nWhether the search was successfully polled after session was saved. Search was added to a session saved object and keepAlive extended." + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", "id": "def-common.ISearchOptions.isRestore", @@ -28211,10 +28380,10 @@ }, { "parentPluginId": "data", - "id": "def-common.SearchSessionFindOptions", + "id": "def-common.SearchSessionRequestInfo", "type": "Interface", "tags": [], - "label": "SearchSessionFindOptions", + "label": "SearchSessionRequestInfo", "description": [], "path": "src/plugins/data/common/search/session/types.ts", "deprecated": false, @@ -28222,55 +28391,12 @@ "children": [ { "parentPluginId": "data", - "id": "def-common.SearchSessionFindOptions.page", - "type": "number", - "tags": [], - "label": "page", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionFindOptions.perPage", - "type": "number", - "tags": [], - "label": "perPage", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionFindOptions.sortField", - "type": "string", - "tags": [], - "label": "sortField", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionFindOptions.sortOrder", + "id": "def-common.SearchSessionRequestInfo.id", "type": "string", "tags": [], - "label": "sortOrder", - "description": [], - "signature": [ - "string | undefined" + "label": "id", + "description": [ + "\nID of the async search request" ], "path": "src/plugins/data/common/search/session/types.ts", "deprecated": false, @@ -28278,13 +28404,12 @@ }, { "parentPluginId": "data", - "id": "def-common.SearchSessionFindOptions.filter", + "id": "def-common.SearchSessionRequestInfo.strategy", "type": "string", "tags": [], - "label": "filter", - "description": [], - "signature": [ - "string | undefined" + "label": "strategy", + "description": [ + "\nSearch strategy used to submit the search request" ], "path": "src/plugins/data/common/search/session/types.ts", "deprecated": false, @@ -28295,10 +28420,10 @@ }, { "parentPluginId": "data", - "id": "def-common.SearchSessionRequestInfo", + "id": "def-common.SearchSessionRequestStatus", "type": "Interface", "tags": [], - "label": "SearchSessionRequestInfo", + "label": "SearchSessionRequestStatus", "description": [], "path": "src/plugins/data/common/search/session/types.ts", "deprecated": false, @@ -28306,38 +28431,19 @@ "children": [ { "parentPluginId": "data", - "id": "def-common.SearchSessionRequestInfo.id", - "type": "string", - "tags": [], - "label": "id", - "description": [ - "\nID of the async search request" - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionRequestInfo.strategy", - "type": "string", - "tags": [], - "label": "strategy", - "description": [ - "\nSearch strategy used to submit the search request" - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionRequestInfo.status", - "type": "string", + "id": "def-common.SearchSessionRequestStatus.status", + "type": "Enum", "tags": [], "label": "status", - "description": [ - "\nstatus" + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchStatus", + "text": "SearchStatus" + } ], "path": "src/plugins/data/common/search/session/types.ts", "deprecated": false, @@ -28345,7 +28451,7 @@ }, { "parentPluginId": "data", - "id": "def-common.SearchSessionRequestInfo.error", + "id": "def-common.SearchSessionRequestStatus.error", "type": "string", "tags": [], "label": "error", @@ -28429,19 +28535,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionSavedObjectAttributes.touched", - "type": "string", - "tags": [], - "label": "touched", - "description": [ - "\nLast touch time of the session" - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "data", "id": "def-common.SearchSessionSavedObjectAttributes.expires", @@ -28455,44 +28548,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionSavedObjectAttributes.completed", - "type": "CompoundType", - "tags": [], - "label": "completed", - "description": [ - "\nTime of transition into completed state,\n\nCan be \"null\" in case already completed session\ntransitioned into in-progress session" - ], - "signature": [ - "string | null | undefined" - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionSavedObjectAttributes.status", - "type": "Enum", - "tags": [], - "label": "status", - "description": [ - "\nstatus" - ], - "signature": [ - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchSessionStatus", - "text": "SearchSessionStatus" - } - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "data", "id": "def-common.SearchSessionSavedObjectAttributes.locatorId", @@ -28567,19 +28622,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "data", - "id": "def-common.SearchSessionSavedObjectAttributes.persisted", - "type": "boolean", - "tags": [], - "label": "persisted", - "description": [ - "\nThis value is true if the session was actively stored by the user. If it is false, the session may be purged by the system." - ], - "path": "src/plugins/data/common/search/session/types.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "data", "id": "def-common.SearchSessionSavedObjectAttributes.realmType", @@ -28636,6 +28678,118 @@ "path": "src/plugins/data/common/search/session/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.SearchSessionSavedObjectAttributes.isCanceled", + "type": "CompoundType", + "tags": [], + "label": "isCanceled", + "description": [ + "\n`true` if session was cancelled" + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data/common/search/session/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.SearchSessionsFindResponse", + "type": "Interface", + "tags": [], + "label": "SearchSessionsFindResponse", + "description": [ + "\nList of search session objects with on-the-fly calculated search session statuses" + ], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionsFindResponse", + "text": "SearchSessionsFindResponse" + }, + " extends ", + "SavedObjectsFindResponse", + "<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionSavedObjectAttributes", + "text": "SearchSessionSavedObjectAttributes" + }, + ", unknown>" + ], + "path": "src/plugins/data/common/search/session/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.SearchSessionsFindResponse.statuses", + "type": "Object", + "tags": [], + "label": "statuses", + "description": [ + "\nMap containing calculated statuses of search sessions from the find response" + ], + "signature": [ + "{ [x: string]: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionStatusResponse", + "text": "SearchSessionStatusResponse" + }, + "; }" + ], + "path": "src/plugins/data/common/search/session/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.SearchSessionStatusResponse", + "type": "Interface", + "tags": [], + "label": "SearchSessionStatusResponse", + "description": [ + "\nOn-the-fly calculated search session status" + ], + "path": "src/plugins/data/common/search/session/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.SearchSessionStatusResponse.status", + "type": "Enum", + "tags": [], + "label": "status", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchSessionStatus", + "text": "SearchSessionStatus" + } + ], + "path": "src/plugins/data/common/search/session/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -29632,6 +29786,18 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "data", + "id": "def-common.SearchStatus", + "type": "Enum", + "tags": [], + "label": "SearchStatus", + "description": [], + "path": "src/plugins/data/common/search/session/status.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "data", "id": "def-common.SortDirection", @@ -32811,7 +32977,7 @@ "signature": [ "{ executionContext?: ", "KibanaExecutionContext", - " | undefined; isStored?: boolean | undefined; isRestore?: boolean | undefined; sessionId?: string | undefined; strategy?: string | undefined; legacyHitsTotal?: boolean | undefined; }" + " | undefined; isStored?: boolean | undefined; isRestore?: boolean | undefined; sessionId?: string | undefined; strategy?: string | undefined; legacyHitsTotal?: boolean | undefined; isSearchStored?: boolean | undefined; }" ], "path": "src/plugins/data/common/search/types.ts", "deprecated": false, diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index ef1fb301e167..6652a2e32c05 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.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 | |-------------------|-----------|------------------------|-----------------| -| 3213 | 33 | 2509 | 23 | +| 3221 | 33 | 2513 | 24 | ## Client diff --git a/api_docs/data_view_editor.devdocs.json b/api_docs/data_view_editor.devdocs.json index e3bf81931775..6ecfaa57fcaa 100644 --- a/api_docs/data_view_editor.devdocs.json +++ b/api_docs/data_view_editor.devdocs.json @@ -151,6 +151,22 @@ "path": "src/plugins/data_view_editor/public/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "dataViewEditor", + "id": "def-public.DataViewEditorProps.showManagementLink", + "type": "CompoundType", + "tags": [], + "label": "showManagementLink", + "description": [ + "\nif set to true a link to the management page is shown" + ], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data_view_editor/public/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 96deb16679bb..49564408adf2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.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 | |-------------------|-----------|------------------------|-----------------| -| 15 | 0 | 7 | 0 | +| 16 | 0 | 7 | 0 | ## Client diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 7935fdee2a27..7985b9c42f2a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 18bcd0b3d618..d298a7341c27 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 4f324ce195e1..6c3fe9e7afdc 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 881f77c01491..62f252081b2c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 0f7512a8f412..37d2e5139c65 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -24,15 +24,15 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | alerting, discover, securitySolution | - | | | stackAlerts, alerting, securitySolution, inputControlVis | - | | | actions, alerting | - | -| | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, securitySolution, actions, alerting, enterpriseSearch, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | -| | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, securitySolution, actions, alerting, enterpriseSearch, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | +| | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | +| | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | | | discover, maps, monitoring | - | | | unifiedSearch, discover, maps, infra, graph, securitySolution, stackAlerts, inputControlVis, savedObjects | - | | | data, discover, embeddable | - | | | advancedSettings, discover | - | | | advancedSettings, discover | - | | | securitySolution | - | -| | encryptedSavedObjects, actions, data, cloud, ml, logstash, securitySolution | - | +| | encryptedSavedObjects, actions, data, ml, logstash, securitySolution, cloudChat | - | | | dashboard, dataVisualizer, stackAlerts, expressionPartitionVis | - | | | dataViews, maps | - | | | dataViews, maps | - | @@ -62,7 +62,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | canvas | - | | | canvas | - | | | canvas | - | -| | cloud | - | | | spaces, savedObjectsManagement | - | | | spaces, savedObjectsManagement | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | @@ -82,7 +81,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | security, fleet | 8.8.0 | | | security, fleet | 8.8.0 | | | management, fleet, security, kibanaOverview, @kbn/core-application-browser-internal, @kbn/core-application-browser-mocks | 8.8.0 | -| | cloud, apm | 8.8.0 | +| | apm | 8.8.0 | | | security, licenseManagement, ml, apm, crossClusterReplication, logstash, painlessLab, searchprofiler, watcher | 8.8.0 | | | security | 8.8.0 | | | mapsEms | 8.8.0 | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index efddd344cdac..4c125f75cfa8 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -173,13 +173,11 @@ so TS and code-reference navigation might not highlight them. | -## cloud +## cloudChat | 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) | - | +| | [chat.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts#:~:text=authc) | - | @@ -656,9 +654,9 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [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 | -| | [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), [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) | - | +| | [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), [use_timeline_save_prompt.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts#:~:text=AppLeaveHandler)+ 1 more | 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) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index a3a41ce841cf..58604d901ae7 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -66,7 +66,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| cloud | | [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud/public/plugin.tsx#:~:text=environment) | 8.8.0 | | kibanaOverview | | [application.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/kibana_overview/public/application.tsx#:~:text=appBasePath), [app_container.tsx](https://github.com/elastic/kibana/tree/main/packages/core/application/core-application-browser-internal/src/ui/app_container.tsx#:~:text=appBasePath), [application_service.mock.ts](https://github.com/elastic/kibana/tree/main/packages/core/application/core-application-browser-mocks/src/application_service.mock.ts#:~:text=appBasePath) | 8.8.0 | | savedObjectsTaggingOss | | [api.ts](https://github.com/elastic/kibana/tree/main/src/plugins/saved_objects_tagging_oss/public/api.ts#:~:text=SavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/saved_objects_tagging_oss/public/decorator/types.ts#:~:text=SavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/saved_objects_tagging_oss/public/decorator/types.ts#:~:text=SavedObject), [api.ts](https://github.com/elastic/kibana/tree/main/src/plugins/saved_objects_tagging_oss/public/api.ts#:~:text=SavedObject), [api.ts](https://github.com/elastic/kibana/tree/main/src/plugins/saved_objects_tagging_oss/public/api.ts#:~:text=SavedObject) | 8.8.0 | | @kbn/core-application-browser-internal | | [app_container.tsx](https://github.com/elastic/kibana/tree/main/packages/core/application/core-application-browser-internal/src/ui/app_container.tsx#:~:text=onAppLeave), [application_service.mock.ts](https://github.com/elastic/kibana/tree/main/packages/core/application/core-application-browser-mocks/src/application_service.mock.ts#:~:text=onAppLeave) | 8.8.0 | @@ -168,7 +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 | | [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 | +| 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), [use_timeline_save_prompt.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts#:~:text=AppLeaveHandler)+ 1 more | 8.8.0 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index d294a0240556..1f599397cba7 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index ddbdd9e335bb..f0b145b742a4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 4cb08ffc96f6..33551d38d8eb 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-10-04 +date: 2022-10-10 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 1559a8abc24e..ac8234957b13 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index ecf61f0f91ce..00b3b6c9f300 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index ba3a3d8c876d..f9dc0ebec4ad 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 276207b2a393..2aef25966075 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 43d88ac7e1ed..4283b8368b2e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 73c8ebf1471b..ff64843fa7ed 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-10-04 +date: 2022-10-10 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 140f35dea7cc..01f3f85e83ae 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-10-04 +date: 2022-10-10 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 5d2d5c6c99e0..d6cf5c3d2575 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 184191c6cd29..1fe3db16572f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 205be987e15e..dd0f849bf2e0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 9d60a4681461..73ebc3735408 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.devdocs.json b/api_docs/expression_legacy_metric_vis.devdocs.json index 8921746a2df8..e1dc4542035a 100644 --- a/api_docs/expression_legacy_metric_vis.devdocs.json +++ b/api_docs/expression_legacy_metric_vis.devdocs.json @@ -629,6 +629,17 @@ "path": "src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "expressionLegacyMetricVis", + "id": "def-common.MetricVisRenderConfig.canNavigateToLens", + "type": "boolean", + "tags": [], + "label": "canNavigateToLens", + "description": [], + "path": "src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index b44d904cdc0e..3a6e7dee6c2b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.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 | |-------------------|-----------|------------------------|-----------------| -| 48 | 0 | 48 | 1 | +| 49 | 0 | 49 | 1 | ## Common diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 22fc0e2475cc..f2a60f4396a7 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index fd1807af21b0..536c4ed7764e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index e0264ca7b2d7..0321ed48ea42 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 071e730b126d..43832994473b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index b964071af3c9..8735e4a7b91b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 3fb4a8e6e654..f10823d12ac9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 4c84f9b68073..ea1cfeadc614 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 8b894b45d78d..5c3926722ab5 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 050f57af12aa..825f04340543 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.devdocs.json b/api_docs/features.devdocs.json index 7f9018f5e89c..a6e977350228 100644 --- a/api_docs/features.devdocs.json +++ b/api_docs/features.devdocs.json @@ -64,7 +64,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -1193,7 +1193,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -2872,7 +2872,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -3122,7 +3122,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -3159,7 +3159,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -3181,7 +3181,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 513017f35056..416f97990d56 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index b49361795cb2..9ead59b5563f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index c431e9780deb..bf3db3e921d0 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-10-04 +date: 2022-10-10 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 7c94fcdac902..aaf7a55eb4c7 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -147,7 +147,7 @@ "section": "def-public.FilesClient", "text": "FilesClient" }, - " extends GlobalEndpoints" + " extends GlobalEndpoints" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -171,7 +171,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>" + "; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -249,7 +249,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>" + "; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -292,7 +292,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "[]; }>" + "[]; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -335,7 +335,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>" + "; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -675,14 +675,15 @@ "\nCreate a files client." ], "signature": [ - "() => ", + "() => ", { "pluginId": "files", "scope": "public", "docId": "kibFilesPluginApi", "section": "def-public.FilesClient", "text": "FilesClient" - } + }, + "" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -700,14 +701,15 @@ "\nCreate a {@link ScopedFileClient} for a given {@link FileKind}.\n" ], "signature": [ - "(fileKind: string) => ", + "(fileKind: string) => ", { "pluginId": "files", "scope": "public", "docId": "kibFilesPluginApi", "section": "def-public.ScopedFilesClient", "text": "ScopedFilesClient" - } + }, + "" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -779,6 +781,61 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "files", + "id": "def-public.Props.meta", + "type": "Object", + "tags": [], + "label": "meta", + "description": [ + "\nImage metadata" + ], + "signature": [ + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileImageMetadata", + "text": "FileImageMetadata" + }, + " | undefined" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.size", + "type": "CompoundType", + "tags": [ + "default" + ], + "label": "size", + "description": [], + "signature": [ + "\"m\" | \"s\" | \"l\" | \"xl\" | \"original\" | \"fullWidth\" | undefined" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.wrapperProps", + "type": "Object", + "tags": [], + "label": "wrapperProps", + "description": [ + "\nProps to pass to the wrapper element" + ], + "signature": [ + "React.HTMLAttributes | undefined" + ], + "path": "x-pack/plugins/files/public/components/image/image.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "files", "id": "def-public.Props.onFirstVisible", @@ -855,7 +912,8 @@ "docId": "kibFilesPluginApi", "section": "def-public.FilesClient", "text": "FilesClient" - } + }, + "" ], "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", "deprecated": false, @@ -1019,7 +1077,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }; delete: { ok: true; }; getById: { file: ", + "; }; delete: { ok: true; }; getById: { file: ", { "pluginId": "files", "scope": "common", @@ -1027,7 +1085,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }; list: { files: ", + "; }; list: { files: ", { "pluginId": "files", "scope": "common", @@ -1035,7 +1093,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "[]; }; update: { file: ", + "[]; }; update: { file: ", { "pluginId": "files", "scope": "common", @@ -1043,7 +1101,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }; upload: { ok: true; size: number; }; download: any; getDownloadHref: string; share: ", + "; }; upload: { ok: true; size: number; }; download: any; getDownloadHref: string; share: ", { "pluginId": "files", "scope": "common", @@ -1108,7 +1166,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; delete: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getById: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ file: ", + "; }>; delete: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getById: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -1116,7 +1174,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; list: (arg?: Omit & { kind: string; }, \"kind\"> | undefined) => Promise<{ files: ", + "; }>; list: (arg?: Omit & { kind: string; }, \"kind\"> | undefined) => Promise<{ files: ", { "pluginId": "files", "scope": "common", @@ -1124,7 +1182,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "[]; }>; update: (arg: Omit | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { kind: string; }, \"kind\">) => Promise<{ file: ", + "[]; }>; update: (arg: Omit | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { kind: string; }, \"kind\">) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -1132,7 +1190,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; upload: (arg: Omit & Readonly<{ selfDestructOnAbort?: boolean | undefined; } & {}> & { body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit; }>; upload: (arg: Omit & Readonly<{ selfDestructOnAbort?: boolean | undefined; } & {}> & { body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit>) | (", - { - "pluginId": "fleet", - "scope": "public", - "docId": "kibFleetPluginApi", - "section": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps", - "text": "PackagePolicyCreateMultiStepExtensionComponentProps" - }, - " & { children?: React.ReactNode; })> & { readonly _result: ", + "React.ExoticComponent<{ children?: React.ReactNode; } | React.RefAttributes>> & { readonly _result: ", { "pluginId": "fleet", "scope": "public", @@ -1329,42 +1305,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "fleet", - "id": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps", - "type": "Interface", - "tags": [], - "label": "PackagePolicyCreateMultiStepExtensionComponentProps", - "description": [], - "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "fleet", - "id": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps.newPolicy", - "type": "Object", - "tags": [], - "label": "newPolicy", - "description": [ - "The integration policy being created" - ], - "signature": [ - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.NewPackagePolicy", - "text": "NewPackagePolicy" - } - ], - "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "fleet", "id": "def-public.PackagePolicyEditExtension", @@ -2287,23 +2227,7 @@ "\nUI Component Extension is used on the pages displaying the ability to Create a multi step\nIntegration Policy" ], "signature": [ - "React.ComponentClass<", - { - "pluginId": "fleet", - "scope": "public", - "docId": "kibFleetPluginApi", - "section": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps", - "text": "PackagePolicyCreateMultiStepExtensionComponentProps" - }, - ", any> | React.FunctionComponent<", - { - "pluginId": "fleet", - "scope": "public", - "docId": "kibFleetPluginApi", - "section": "def-public.PackagePolicyCreateMultiStepExtensionComponentProps", - "text": "PackagePolicyCreateMultiStepExtensionComponentProps" - }, - ">" + "React.ComponentClass<{}, any> | React.FunctionComponent<{}>" ], "path": "x-pack/plugins/fleet/public/types/ui_extensions.ts", "deprecated": false, @@ -8262,6 +8186,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.Agent.outputs", + "type": "Object", + "tags": [], + "label": "outputs", + "description": [], + "signature": [ + "Record | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/agent.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.Agent.status", @@ -14105,7 +14043,7 @@ "section": "def-common.RegistryPackage", "text": "RegistryPackage" }, - ", \"internal\" | \"assets\" | \"readme\" | \"data_streams\" | \"elasticsearch\">" + ", \"elasticsearch\" | \"internal\" | \"assets\" | \"readme\" | \"data_streams\">" ], "path": "x-pack/plugins/fleet/common/types/models/epm.ts", "deprecated": false, diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 0dc50255cea6..82149c338ffd 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-10-04 +date: 2022-10-10 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 | |-------------------|-----------|------------------------|-----------------| -| 997 | 3 | 893 | 17 | +| 996 | 3 | 893 | 17 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index b4fff902dfab..bd9ccebf846d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.devdocs.json b/api_docs/guided_onboarding.devdocs.json index 8b44efa700b6..5b5809dbbe1e 100644 --- a/api_docs/guided_onboarding.devdocs.json +++ b/api_docs/guided_onboarding.devdocs.json @@ -94,7 +94,7 @@ "label": "id", "description": [], "signature": [ - "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alerts\" | \"cases\" | \"browse_docs\" | \"search_experience\"" + "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alertsCases\" | \"browse_docs\" | \"search_experience\"" ], "path": "src/plugins/guided_onboarding/common/types.ts", "deprecated": false, @@ -143,7 +143,7 @@ "label": "GuideStepIds", "description": [], "signature": [ - "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alerts\" | \"cases\" | \"browse_docs\" | \"search_experience\"" + "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alertsCases\" | \"browse_docs\" | \"search_experience\"" ], "path": "src/plugins/guided_onboarding/common/types.ts", "deprecated": false, @@ -242,7 +242,7 @@ "label": "guidedOnboardingApi", "description": [], "signature": [ - "ApiService", + "GuidedOnboardingApi", " | undefined" ], "path": "src/plugins/guided_onboarding/public/types.ts", diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 9aa86b82df10..1021f353f39d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.devdocs.json b/api_docs/home.devdocs.json index c8e980ff0a0a..a18ea6d54413 100644 --- a/api_docs/home.devdocs.json +++ b/api_docs/home.devdocs.json @@ -1313,10 +1313,6 @@ "removeBy": "8.8.0", "trackAdoption": false, "references": [ - { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/public/plugin.tsx" - }, { "plugin": "apm", "path": "x-pack/plugins/apm/public/plugin.ts" diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 4c129f6d92a4..a1269fecd710 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-10-04 +date: 2022-10-10 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 d6393f4b4439..d64fb3896ad9 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-10-04 +date: 2022-10-10 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 b7f14de7ee10..c0db624b8d38 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-10-04 +date: 2022-10-10 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 ea6ecce3d4d9..4e9dd1b904ed 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-10-04 +date: 2022-10-10 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 c30aea80c5cb..c81edba15ec6 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index f4a2df60fa9e..208c8049e38b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 0dcbd76b6b45..083f69d0939a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index ac530589f0fb..61d044d8d2f4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index c87bec0a9bb9..3cd0f613a9e3 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-10-04 +date: 2022-10-10 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 eb196b2621ae..e177b66a8583 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-10-04 +date: 2022-10-10 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 bc4a0560544d..b7fc34693110 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-10-04 +date: 2022-10-10 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 a86d78bf6e57..28a6f6bafc1a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 42bf70404bb6..070f03039547 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index a3faccdf22fc..8c7ceac1d78f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 263d6af10941..7a21c30fd384 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 1f272ffdf3ec..c1926cbf98c9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 9137a34ac7cf..c9629dc573b1 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 69b4ee9c3bd0..44d2e38a9537 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-10-04 +date: 2022-10-10 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 e8948c5151a3..e96342c7ce84 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-10-04 +date: 2022-10-10 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 1660f71a7d42..3abeee268d46 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-10-04 +date: 2022-10-10 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 548fc0c4f742..45276f22d358 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index b57e2abe5c1d..cd266cfa11bc 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index f8182e50d0e1..a975d89c203c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 87058eea7d0c..254af0805e3c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index 399bb2c474d9..2639fcb9a105 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index d3a842a24ce8..ae700c05f364 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 8a6f64ea706c..ffebb3323e82 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 175a3130aaf9..177cc1d2c88f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index d369d314f87e..668d543ce208 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 9dba4bbf3a30..8ec280432b05 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-10-04 +date: 2022-10-10 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 3220f4f43ec6..18c7af5ca754 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index bc77e3013d70..6a584f10cc26 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index fe9a5a7a8033..56ae80db6296 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 8ba5c5458189..ae1bff84123b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index b32913e076b1..206cc86999bb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index ad1626e300d3..10f93de93e69 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index cb8729ded12a..2220baea064f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 01f8418de314..2451c9adfa29 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 8325f55e3db4..6dffd3fe3eab 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index bcd417fbc9ed..374a205d6d7e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 507b0ccfcab4..d77d53ffa191 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 0a3625bef942..a0adf2433ba9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 178a8ff27c8e..ff353e720658 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index e634f6c30bfc..b1482ac80cfc 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 1c4445244f83..3052f18231f8 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 84bb74021f2b..a374fa4b72bf 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 52fe8e282031..1452d1eca68d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index f2e9fe632436..2fa1a2398f3c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index b2f4b911b54f..58247b909688 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-10-04 +date: 2022-10-10 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 dd0bc1fd746e..0c2303c8d7b4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 598dc5403887..acbec545278f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index a5854e8359bc..6b49aeb6ad2d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index b189b4cda1ce..758606f2c966 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 89d13dd6c05e..8ef722c82ebb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 0ca986ec796b..75bee5daa99f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 54f912532a0f..ff88c65e59c6 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 41e3c5f4eb0d..a64b529d30a9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 0511dc3c9909..cf83c7e9bf2f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 451bb29fe60c..7786d5b668cf 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 9d72c4c54543..403da6112f33 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 9640ef186485..1f6219d03955 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index f5bb1f42b22a..b28f3f07c970 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index efae931fc186..2aae17b574f6 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 2af154133867..d646839a1a1c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.devdocs.json b/api_docs/kbn_core_elasticsearch_client_server_internal.devdocs.json index 0491f4259431..aa5dd46285c2 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.devdocs.json @@ -21,10 +21,10 @@ "signature": [ "(config: ", "ElasticsearchClientConfig", - ", { logger, type, scoped, getExecutionContext, agentManager, kibanaVersion, }: { logger: ", + ", { logger, type, scoped, getExecutionContext, agentFactoryProvider, kibanaVersion, }: { logger: ", "Logger", - "; type: string; scoped?: boolean | undefined; getExecutionContext?: (() => string | undefined) | undefined; agentManager: ", - "AgentManager", + "; type: string; scoped?: boolean | undefined; getExecutionContext?: (() => string | undefined) | undefined; agentFactoryProvider: ", + "AgentFactoryProvider", "; kibanaVersion: string; }) => ", "default" ], @@ -52,7 +52,7 @@ "id": "def-server.configureClient.$2", "type": "Object", "tags": [], - "label": "{\n logger,\n type,\n scoped = false,\n getExecutionContext = noop,\n agentManager,\n kibanaVersion,\n }", + "label": "{\n logger,\n type,\n scoped = false,\n getExecutionContext = noop,\n agentFactoryProvider,\n kibanaVersion,\n }", "description": [], "path": "packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts", "deprecated": false, @@ -115,13 +115,13 @@ }, { "parentPluginId": "@kbn/core-elasticsearch-client-server-internal", - "id": "def-server.configureClient.$2.agentManager", + "id": "def-server.configureClient.$2.agentFactoryProvider", "type": "Object", "tags": [], - "label": "agentManager", + "label": "agentFactoryProvider", "description": [], "signature": [ - "AgentManager" + "AgentFactoryProvider" ], "path": "packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts", "deprecated": false, @@ -220,7 +220,40 @@ "initialIsOpen": false } ], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "@kbn/core-elasticsearch-client-server-internal", + "id": "def-server.AgentStore", + "type": "Interface", + "tags": [], + "label": "AgentStore", + "description": [], + "path": "packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-elasticsearch-client-server-internal", + "id": "def-server.AgentStore.getAgents", + "type": "Function", + "tags": [], + "label": "getAgents", + "description": [], + "signature": [ + "() => Set<", + "NetworkAgent", + ">" + ], + "path": "packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [], "objects": [] diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 1454820b8fa4..bbc274c92559 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; @@ -21,10 +21,13 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 13 | 0 | 11 | 1 | +| 15 | 0 | 13 | 2 | ## Server ### Functions +### Interfaces + + diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json b/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json index c682403d3d63..dc17ba9d371c 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json @@ -10,7 +10,46 @@ }, "server": { "classes": [], - "functions": [], + "functions": [ + { + "parentPluginId": "@kbn/core-elasticsearch-client-server-mocks", + "id": "def-server.createAgentStoreMock", + "type": "Function", + "tags": [], + "label": "createAgentStoreMock", + "description": [], + "signature": [ + "(agents?: Set<", + "NetworkAgent", + ">) => ", + "AgentStore" + ], + "path": "packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-elasticsearch-client-server-mocks", + "id": "def-server.createAgentStoreMock.$1", + "type": "Object", + "tags": [], + "label": "agents", + "description": [], + "signature": [ + "Set<", + "NetworkAgent", + ">" + ], + "path": "packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [ { "parentPluginId": "@kbn/core-elasticsearch-client-server-mocks", diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 1385f7dc0e1f..f13371eb32d3 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; @@ -21,13 +21,16 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 36 | 1 | 32 | 0 | +| 38 | 1 | 34 | 0 | ## Server ### Objects +### Functions + + ### Interfaces diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 1deae83f6587..9a166f9484d6 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 15117157d71d..b7f7ae66ec61 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 493f5c036f9a..7651de084b9a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 9910a744c62d..61c1ac3f1ac5 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index b9c83fd47632..49ddcada894e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index e152e5bb83a2..03a2cd43f914 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 9392cffe6209..411798fcb340 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 3e511fafa945..380ab162b9c6 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index ec1debc1c012..fb62ecaef4a4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index eed2089898a8..45029383a8e0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 87ae45af703a..c8de0fc49cd9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index b77bce9a27db..41b4cdf396fa 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index bfc781548810..b0aebe4d000f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 6f305680273e..b09cce2f1059 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 22b6b9c66633..2d5738cf8ed2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 579277f0e7c5..a5986b8db688 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 45b83d9e361c..ec1bafeee45e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index b063b1ec8777..7b60c4634ffa 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index e42402eeb736..acbda3533b88 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index d636450713c7..533d1af83814 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.devdocs.json b/api_docs/kbn_core_http_resources_server.devdocs.json new file mode 100644 index 000000000000..4a58d5e1ad9e --- /dev/null +++ b/api_docs/kbn_core_http_resources_server.devdocs.json @@ -0,0 +1,457 @@ +{ + "id": "@kbn/core-http-resources-server", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResources", + "type": "Interface", + "tags": [], + "label": "HttpResources", + "description": [ + "\nHttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP.\nProvides API allowing plug-ins to respond with:\n- a pre-configured HTML page bootstrapping Kibana client app\n- custom HTML page\n- custom JS script file." + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResources.register", + "type": "Function", + "tags": [], + "label": "register", + "description": [ + "To register a route handler executing passed function to form response." + ], + "signature": [ + "(route: ", + "RouteConfig", + ", handler: ", + { + "pluginId": "@kbn/core-http-resources-server", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerPluginApi", + "section": "def-server.HttpResourcesRequestHandler", + "text": "HttpResourcesRequestHandler" + }, + ") => void" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResources.register.$1", + "type": "Object", + "tags": [], + "label": "route", + "description": [], + "signature": [ + "RouteConfig", + "" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResources.register.$2", + "type": "Function", + "tags": [], + "label": "handler", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-resources-server", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerPluginApi", + "section": "def-server.HttpResourcesRequestHandler", + "text": "HttpResourcesRequestHandler" + }, + "" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesRenderOptions", + "type": "Interface", + "tags": [], + "label": "HttpResourcesRenderOptions", + "description": [ + "\nAllows to configure HTTP response parameters" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesRenderOptions.headers", + "type": "CompoundType", + "tags": [], + "label": "headers", + "description": [ + "\nHTTP Headers with additional information about response." + ], + "signature": [ + "ResponseHeaders", + " | undefined" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit", + "type": "Interface", + "tags": [], + "label": "HttpResourcesServiceToolkit", + "description": [ + "\nExtended set of {@link KibanaResponseFactory} helpers used to respond with HTML or JS resource." + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit.renderCoreApp", + "type": "Function", + "tags": [], + "label": "renderCoreApp", + "description": [ + "To respond with HTML page bootstrapping Kibana application." + ], + "signature": [ + "(options?: ", + { + "pluginId": "@kbn/core-http-resources-server", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerPluginApi", + "section": "def-server.HttpResourcesRenderOptions", + "text": "HttpResourcesRenderOptions" + }, + " | undefined) => Promise<", + "IKibanaResponse", + ">" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit.renderCoreApp.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-resources-server", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerPluginApi", + "section": "def-server.HttpResourcesRenderOptions", + "text": "HttpResourcesRenderOptions" + }, + " | undefined" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit.renderAnonymousCoreApp", + "type": "Function", + "tags": [], + "label": "renderAnonymousCoreApp", + "description": [ + "To respond with HTML page bootstrapping Kibana application without retrieving user-specific information." + ], + "signature": [ + "(options?: ", + { + "pluginId": "@kbn/core-http-resources-server", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerPluginApi", + "section": "def-server.HttpResourcesRenderOptions", + "text": "HttpResourcesRenderOptions" + }, + " | undefined) => Promise<", + "IKibanaResponse", + ">" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit.renderAnonymousCoreApp.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-resources-server", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerPluginApi", + "section": "def-server.HttpResourcesRenderOptions", + "text": "HttpResourcesRenderOptions" + }, + " | undefined" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit.renderHtml", + "type": "Function", + "tags": [], + "label": "renderHtml", + "description": [ + "To respond with a custom HTML page." + ], + "signature": [ + "(options: ", + "HttpResponseOptions", + ") => ", + "IKibanaResponse", + "" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit.renderHtml.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "HttpResponseOptions" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit.renderJs", + "type": "Function", + "tags": [], + "label": "renderJs", + "description": [ + "To respond with a custom JS script file." + ], + "signature": [ + "(options: ", + "HttpResponseOptions", + ") => ", + "IKibanaResponse", + "" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesServiceToolkit.renderJs.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "HttpResponseOptions" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesRequestHandler", + "type": "Type", + "tags": [], + "label": "HttpResourcesRequestHandler", + "description": [ + "\nExtended version of {@link RequestHandler} having access to {@link HttpResourcesServiceToolkit}\nto respond with HTML or JS resources." + ], + "signature": [ + "(context: Context, request: ", + "KibanaRequest", + ", response: ", + "KibanaSuccessResponseFactory", + " & ", + "KibanaRedirectionResponseFactory", + " & ", + "KibanaErrorResponseFactory", + " & { custom | Error | ", + "Stream", + " | Buffer | { message: string | Error; attributes?: ", + "ResponseErrorAttributes", + " | undefined; } | undefined>(options: ", + "CustomHttpResponseOptions", + "): ", + "IKibanaResponse", + "; } & ", + { + "pluginId": "@kbn/core-http-resources-server", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerPluginApi", + "section": "def-server.HttpResourcesServiceToolkit", + "text": "HttpResourcesServiceToolkit" + }, + ") => ", + "IKibanaResponse", + " | Promise<", + "IKibanaResponse", + ">" + ], + "path": "packages/core/http/core-http-resources-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesRequestHandler.$1", + "type": "Uncategorized", + "tags": [], + "label": "context", + "description": [ + "{@link RequestHandlerContext } - the core context exposed for this request." + ], + "signature": [ + "Context" + ], + "path": "node_modules/@types/kbn__core-http-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesRequestHandler.$2", + "type": "Object", + "tags": [], + "label": "request", + "description": [ + "{@link KibanaRequest } - object containing information about requested resource,\nsuch as path, method, headers, parameters, query, body, etc." + ], + "signature": [ + "KibanaRequest", + "" + ], + "path": "node_modules/@types/kbn__core-http-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesRequestHandler.$3", + "type": "Uncategorized", + "tags": [], + "label": "response", + "description": [ + "{@link KibanaResponseFactory } {@libk HttpResourcesServiceToolkit} - a set of helper functions used to respond to a request." + ], + "signature": [ + "ResponseFactory" + ], + "path": "node_modules/@types/kbn__core-http-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-http-resources-server", + "id": "def-server.HttpResourcesResponseOptions", + "type": "Type", + "tags": [], + "label": "HttpResourcesResponseOptions", + "description": [ + "\nHTTP Resources response parameters" + ], + "signature": [ + "HttpResponseOptions" + ], + "path": "packages/core/http/core-http-resources-server/src/types.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_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx new file mode 100644 index 000000000000..d7c3c5d569dd --- /dev/null +++ b/api_docs/kbn_core_http_resources_server.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: kibKbnCoreHttpResourcesServerPluginApi +slug: /kibana-dev-docs/api/kbn-core-http-resources-server +title: "@kbn/core-http-resources-server" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-http-resources-server plugin +date: 2022-10-10 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] +--- +import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; + + + +Contact [Owner missing] for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 20 | 0 | 6 | 0 | + +## Server + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_core_http_resources_server_internal.devdocs.json b/api_docs/kbn_core_http_resources_server_internal.devdocs.json new file mode 100644 index 000000000000..fae7163d58b8 --- /dev/null +++ b/api_docs/kbn_core_http_resources_server_internal.devdocs.json @@ -0,0 +1,200 @@ +{ + "id": "@kbn/core-http-resources-server-internal", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [ + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService", + "type": "Class", + "tags": [], + "label": "HttpResourcesService", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-resources-server-internal", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerInternalPluginApi", + "section": "def-server.HttpResourcesService", + "text": "HttpResourcesService" + }, + " implements ", + "CoreService", + "<", + "InternalHttpResourcesPreboot", + ", void>" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "CoreContext" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService.preboot", + "type": "Function", + "tags": [], + "label": "preboot", + "description": [], + "signature": [ + "(deps: ", + "PrebootDeps", + ") => { createRegistrar: (router: ", + "IRouter", + "<", + "RequestHandlerContext", + ">) => ", + "HttpResources", + "; }" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService.preboot.$1", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "PrebootDeps" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService.setup", + "type": "Function", + "tags": [], + "label": "setup", + "description": [], + "signature": [ + "(deps: ", + "SetupDeps", + ") => { createRegistrar: (router: ", + "IRouter", + "<", + "RequestHandlerContext", + ">) => ", + "HttpResources", + "; }" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService.setup.$1", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "SetupDeps" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService.start", + "type": "Function", + "tags": [], + "label": "start", + "description": [], + "signature": [ + "() => void" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server-internal", + "id": "def-server.HttpResourcesService.stop", + "type": "Function", + "tags": [], + "label": "stop", + "description": [], + "signature": [ + "() => void" + ], + "path": "packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx new file mode 100644 index 000000000000..100517bf79a7 --- /dev/null +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCoreHttpResourcesServerInternalPluginApi +slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal +title: "@kbn/core-http-resources-server-internal" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-http-resources-server-internal plugin +date: 2022-10-10 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] +--- +import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; + + + +Contact [Owner missing] for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 9 | 0 | 9 | 3 | + +## Server + +### Classes + + diff --git a/api_docs/kbn_core_http_resources_server_mocks.devdocs.json b/api_docs/kbn_core_http_resources_server_mocks.devdocs.json new file mode 100644 index 000000000000..619ba47e29df --- /dev/null +++ b/api_docs/kbn_core_http_resources_server_mocks.devdocs.json @@ -0,0 +1,307 @@ +{ + "id": "@kbn/core-http-resources-server-mocks", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/core-http-resources-server-mocks", + "id": "def-server.HttpResourcesMock", + "type": "Type", + "tags": [], + "label": "HttpResourcesMock", + "description": [], + "signature": [ + "{ setup: jest.MockInstance<{ createRegistrar: (router: ", + "IRouter", + "<", + "RequestHandlerContext", + ">) => ", + "HttpResources", + "; }, [deps: ", + "SetupDeps", + "]>; start: jest.MockInstance; stop: jest.MockInstance; preboot: jest.MockInstance<{ createRegistrar: (router: ", + "IRouter", + "<", + "RequestHandlerContext", + ">) => ", + "HttpResources", + "; }, [deps: ", + "PrebootDeps", + "]>; } & ", + "PublicMethodsOf", + "<", + "HttpResourcesService", + ">" + ], + "path": "packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/core-http-resources-server-mocks", + "id": "def-server.httpResourcesMock", + "type": "Object", + "tags": [], + "label": "httpResourcesMock", + "description": [], + "path": "packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-resources-server-mocks", + "id": "def-server.httpResourcesMock.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "@kbn/core-http-resources-server-mocks", + "scope": "server", + "docId": "kibKbnCoreHttpResourcesServerMocksPluginApi", + "section": "def-server.HttpResourcesMock", + "text": "HttpResourcesMock" + } + ], + "path": "packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server-mocks", + "id": "def-server.httpResourcesMock.createRegistrar", + "type": "Function", + "tags": [], + "label": "createRegistrar", + "description": [], + "signature": [ + "() => jest.Mocked<", + "HttpResources", + ">" + ], + "path": "packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server-mocks", + "id": "def-server.httpResourcesMock.createPrebootContract", + "type": "Function", + "tags": [], + "label": "createPrebootContract", + "description": [], + "signature": [ + "() => { createRegistrar: jest.Mock, []>; }" + ], + "path": "packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server-mocks", + "id": "def-server.httpResourcesMock.createSetupContract", + "type": "Function", + "tags": [], + "label": "createSetupContract", + "description": [], + "signature": [ + "() => { createRegistrar: jest.Mock, []>; }" + ], + "path": "packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-http-resources-server-mocks", + "id": "def-server.httpResourcesMock.createResponseFactory", + "type": "Function", + "tags": [], + "label": "createResponseFactory", + "description": [], + "signature": [ + "() => { renderCoreApp: jest.MockInstance>, [options?: ", + "HttpResourcesRenderOptions", + " | undefined]> & ((options?: ", + "HttpResourcesRenderOptions", + " | undefined) => Promise<", + "IKibanaResponse", + ">); renderAnonymousCoreApp: jest.MockInstance>, [options?: ", + "HttpResourcesRenderOptions", + " | undefined]> & ((options?: ", + "HttpResourcesRenderOptions", + " | undefined) => Promise<", + "IKibanaResponse", + ">); renderHtml: jest.MockInstance<", + "IKibanaResponse", + ", [options: ", + "HttpResponseOptions", + "]> & ((options: ", + "HttpResponseOptions", + ") => ", + "IKibanaResponse", + "); renderJs: jest.MockInstance<", + "IKibanaResponse", + ", [options: ", + "HttpResponseOptions", + "]> & ((options: ", + "HttpResponseOptions", + ") => ", + "IKibanaResponse", + "); ok: jest.MockInstance<", + "IKibanaResponse", + ", [options?: ", + "HttpResponseOptions", + " | undefined]> & ((options?: ", + "HttpResponseOptions", + " | undefined) => ", + "IKibanaResponse", + "); accepted: jest.MockInstance<", + "IKibanaResponse", + ", [options?: ", + "HttpResponseOptions", + " | undefined]> & ((options?: ", + "HttpResponseOptions", + " | undefined) => ", + "IKibanaResponse", + "); noContent: jest.MockInstance<", + "IKibanaResponse", + ", [options?: ", + "HttpResponseOptions", + " | undefined]> & ((options?: ", + "HttpResponseOptions", + " | undefined) => ", + "IKibanaResponse", + "); redirected: jest.MockInstance<", + "IKibanaResponse", + ", [options: ", + "RedirectResponseOptions", + "]> & ((options: ", + "RedirectResponseOptions", + ") => ", + "IKibanaResponse", + "); badRequest: jest.MockInstance<", + "IKibanaResponse", + ", [options?: ", + "ErrorHttpResponseOptions", + " | undefined]> & ((options?: ", + "ErrorHttpResponseOptions", + " | undefined) => ", + "IKibanaResponse", + "); unauthorized: jest.MockInstance<", + "IKibanaResponse", + ", [options?: ", + "ErrorHttpResponseOptions", + " | undefined]> & ((options?: ", + "ErrorHttpResponseOptions", + " | undefined) => ", + "IKibanaResponse", + "); forbidden: jest.MockInstance<", + "IKibanaResponse", + ", [options?: ", + "ErrorHttpResponseOptions", + " | undefined]> & ((options?: ", + "ErrorHttpResponseOptions", + " | undefined) => ", + "IKibanaResponse", + "); notFound: jest.MockInstance<", + "IKibanaResponse", + ", [options?: ", + "ErrorHttpResponseOptions", + " | undefined]> & ((options?: ", + "ErrorHttpResponseOptions", + " | undefined) => ", + "IKibanaResponse", + "); conflict: jest.MockInstance<", + "IKibanaResponse", + ", [options?: ", + "ErrorHttpResponseOptions", + " | undefined]> & ((options?: ", + "ErrorHttpResponseOptions", + " | undefined) => ", + "IKibanaResponse", + "); customError: jest.MockInstance<", + "IKibanaResponse", + ", [options: ", + "CustomHttpResponseOptions", + "<", + "Stream", + " | Buffer | ", + "ResponseError", + ">]> & ((options: ", + "CustomHttpResponseOptions", + "<", + "Stream", + " | Buffer | ", + "ResponseError", + ">) => ", + "IKibanaResponse", + "); custom: jest.MockInstance<", + "IKibanaResponse", + ", [options: ", + "CustomHttpResponseOptions", + " | Error | ", + "Stream", + " | Buffer | { message: string | Error; attributes?: ", + "ResponseErrorAttributes", + " | undefined; } | undefined>]> & ( | Error | ", + "Stream", + " | Buffer | { message: string | Error; attributes?: ", + "ResponseErrorAttributes", + " | undefined; } | undefined>(options: ", + "CustomHttpResponseOptions", + ") => ", + "IKibanaResponse", + "); }" + ], + "path": "packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ], + "initialIsOpen": false + } + ] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx new file mode 100644 index 000000000000..a7ccc3b652e7 --- /dev/null +++ b/api_docs/kbn_core_http_resources_server_mocks.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: kibKbnCoreHttpResourcesServerMocksPluginApi +slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks +title: "@kbn/core-http-resources-server-mocks" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-http-resources-server-mocks plugin +date: 2022-10-10 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] +--- +import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 7 | 0 | 7 | 0 | + +## Server + +### Objects + + +### Consts, variables and types + + diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 5691276cc4c8..19c25e8b0395 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 5d087fbc1259..414aad8ed5b8 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 87f06ed8d63e..8b9c843e68fc 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 266b3696995f..abe1586bedf1 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index c2e9cef4a3f1..05a8f0280849 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 23d1612d624c..4db46422eeff 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 2809246ccc67..77771a9c4e01 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index d8a8f0c780f5..102e9d27d27d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 34bcc3c6cdcf..ce3c5558f938 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 0616e05e0023..346b9f5a1a97 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index d494ce28acb1..dc28aa0d05f9 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-10-04 +date: 2022-10-10 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 30217e2fc3ff..f17c613a2952 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 5e7ba108a654..e9103d35d715 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index a6e9fb32a00b..86bc7cd276cb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 531b5b53a829..e8d18ffa8599 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 2e32f2efcec2..2da058044c31 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 15c79e1a0db3..0fd6658b66cc 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index cd7319b284fb..5ea9405ce0bd 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 93667b80ba7f..b33d09367059 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.devdocs.json b/api_docs/kbn_core_metrics_collectors_server_internal.devdocs.json index 0de9634445cc..8713e12e2a39 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.devdocs.json +++ b/api_docs/kbn_core_metrics_collectors_server_internal.devdocs.json @@ -10,6 +10,100 @@ }, "server": { "classes": [ + { + "parentPluginId": "@kbn/core-metrics-collectors-server-internal", + "id": "def-server.ElasticsearchClientsMetricsCollector", + "type": "Class", + "tags": [], + "label": "ElasticsearchClientsMetricsCollector", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-metrics-collectors-server-internal", + "scope": "server", + "docId": "kibKbnCoreMetricsCollectorsServerInternalPluginApi", + "section": "def-server.ElasticsearchClientsMetricsCollector", + "text": "ElasticsearchClientsMetricsCollector" + }, + " implements ", + "MetricsCollector", + "<", + "ElasticsearchClientsMetrics", + ">" + ], + "path": "packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-metrics-collectors-server-internal", + "id": "def-server.ElasticsearchClientsMetricsCollector.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-metrics-collectors-server-internal", + "id": "def-server.ElasticsearchClientsMetricsCollector.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "agentStore", + "description": [], + "signature": [ + "AgentStore" + ], + "path": "packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-metrics-collectors-server-internal", + "id": "def-server.ElasticsearchClientsMetricsCollector.collect", + "type": "Function", + "tags": [], + "label": "collect", + "description": [], + "signature": [ + "() => Promise<", + "ElasticsearchClientsMetrics", + ">" + ], + "path": "packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-metrics-collectors-server-internal", + "id": "def-server.ElasticsearchClientsMetricsCollector.reset", + "type": "Function", + "tags": [], + "label": "reset", + "description": [], + "signature": [ + "() => void" + ], + "path": "packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-metrics-collectors-server-internal", "id": "def-server.EventLoopDelaysMonitor", diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 649123ca8c6a..b6588e68f0ad 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_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 | |-------------------|-----------|------------------------|-----------------| -| 24 | 0 | 20 | 0 | +| 29 | 0 | 25 | 0 | ## Server diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 21c886d8ce97..9f08d7e3befd 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.devdocs.json b/api_docs/kbn_core_metrics_server.devdocs.json index 77ae844e9399..6413560fea45 100644 --- a/api_docs/kbn_core_metrics_server.devdocs.json +++ b/api_docs/kbn_core_metrics_server.devdocs.json @@ -12,6 +12,168 @@ "classes": [], "functions": [], "interfaces": [ + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics", + "type": "Interface", + "tags": [], + "label": "ElasticsearchClientsMetrics", + "description": [ + "\nMetrics related to the elasticsearch clients" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.protocol", + "type": "CompoundType", + "tags": [], + "label": "protocol", + "description": [ + "The protocol (or protocols) that these Agents are using" + ], + "signature": [ + "\"http\" | \"none\" | \"mixed\" | \"https\"" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.connectedNodes", + "type": "number", + "tags": [], + "label": "connectedNodes", + "description": [ + "Number of ES nodes that ES-js client is connecting to" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.nodesWithActiveSockets", + "type": "number", + "tags": [], + "label": "nodesWithActiveSockets", + "description": [ + "Number of nodes with active connections" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.nodesWithIdleSockets", + "type": "number", + "tags": [], + "label": "nodesWithIdleSockets", + "description": [ + "Number of nodes with available connections (alive but idle).\nNote that a node can have both active and idle connections at the same time" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.totalActiveSockets", + "type": "number", + "tags": [], + "label": "totalActiveSockets", + "description": [ + "Total number of active sockets (all nodes, all connections)" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.totalIdleSockets", + "type": "number", + "tags": [], + "label": "totalIdleSockets", + "description": [ + "Total number of available sockets (alive but idle, all nodes, all connections)" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.totalQueuedRequests", + "type": "number", + "tags": [], + "label": "totalQueuedRequests", + "description": [ + "Total number of queued requests (all nodes, all connections)" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.mostActiveNodeSockets", + "type": "number", + "tags": [], + "label": "mostActiveNodeSockets", + "description": [ + "Number of active connections of the node with most active connections" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.averageActiveSocketsPerNode", + "type": "number", + "tags": [], + "label": "averageActiveSocketsPerNode", + "description": [ + "Average of active sockets per node (all connections)" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.mostIdleNodeSockets", + "type": "number", + "tags": [], + "label": "mostIdleNodeSockets", + "description": [ + "Number of idle connections of the node with most idle connections" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientsMetrics.averageIdleSocketsPerNode", + "type": "number", + "tags": [], + "label": "averageIdleSocketsPerNode", + "description": [ + "Average of available (idle) sockets per node (all connections)" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-metrics-server", "id": "def-server.IEventLoopDelaysMonitor", @@ -349,6 +511,28 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.OpsMetrics.elasticsearch_client", + "type": "Object", + "tags": [], + "label": "elasticsearch_client", + "description": [ + "\nMetrics related to the elasticsearch client" + ], + "signature": [ + { + "pluginId": "@kbn/core-metrics-server", + "scope": "server", + "docId": "kibKbnCoreMetricsServerPluginApi", + "section": "def-server.ElasticsearchClientsMetrics", + "text": "ElasticsearchClientsMetrics" + } + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-metrics-server", "id": "def-server.OpsMetrics.process", @@ -779,6 +963,23 @@ ], "enums": [], "misc": [ + { + "parentPluginId": "@kbn/core-metrics-server", + "id": "def-server.ElasticsearchClientProtocol", + "type": "Type", + "tags": [], + "label": "ElasticsearchClientProtocol", + "description": [ + "\nProtocol(s) used by the Elasticsearch Client" + ], + "signature": [ + "\"http\" | \"none\" | \"mixed\" | \"https\"" + ], + "path": "packages/core/metrics/core-metrics-server/src/metrics.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-metrics-server", "id": "def-server.MetricsServiceStart", diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index a3b481458c97..e8eb390e0729 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_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 | |-------------------|-----------|------------------------|-----------------| -| 48 | 0 | 8 | 0 | +| 62 | 0 | 8 | 0 | ## Server diff --git a/api_docs/kbn_core_metrics_server_internal.devdocs.json b/api_docs/kbn_core_metrics_server_internal.devdocs.json index dfdb1f7c95ef..0920f42e9bf8 100644 --- a/api_docs/kbn_core_metrics_server_internal.devdocs.json +++ b/api_docs/kbn_core_metrics_server_internal.devdocs.json @@ -36,6 +36,20 @@ "path": "packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-internal", + "id": "def-server.MetricsServiceSetupDeps.elasticsearchService", + "type": "Object", + "tags": [], + "label": "elasticsearchService", + "description": [], + "signature": [ + "InternalElasticsearchServiceSetup" + ], + "path": "packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index b78cf2f752e5..16403bb05f5a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_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 | |-------------------|-----------|------------------------|-----------------| -| 5 | 0 | 5 | 0 | +| 6 | 0 | 6 | 0 | ## Server diff --git a/api_docs/kbn_core_metrics_server_mocks.devdocs.json b/api_docs/kbn_core_metrics_server_mocks.devdocs.json index 1ca231c151d2..9e06f6bcaef0 100644 --- a/api_docs/kbn_core_metrics_server_mocks.devdocs.json +++ b/api_docs/kbn_core_metrics_server_mocks.devdocs.json @@ -133,6 +133,144 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics", + "type": "Object", + "tags": [], + "label": "sampleEsClientMetrics", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.protocol", + "type": "string", + "tags": [], + "label": "protocol", + "description": [], + "signature": [ + "\"https\"" + ], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.connectedNodes", + "type": "number", + "tags": [], + "label": "connectedNodes", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.nodesWithActiveSockets", + "type": "number", + "tags": [], + "label": "nodesWithActiveSockets", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.nodesWithIdleSockets", + "type": "number", + "tags": [], + "label": "nodesWithIdleSockets", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.totalActiveSockets", + "type": "number", + "tags": [], + "label": "totalActiveSockets", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.totalIdleSockets", + "type": "number", + "tags": [], + "label": "totalIdleSockets", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.totalQueuedRequests", + "type": "number", + "tags": [], + "label": "totalQueuedRequests", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.mostActiveNodeSockets", + "type": "number", + "tags": [], + "label": "mostActiveNodeSockets", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.averageActiveSocketsPerNode", + "type": "number", + "tags": [], + "label": "averageActiveSocketsPerNode", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.mostIdleNodeSockets", + "type": "number", + "tags": [], + "label": "mostIdleNodeSockets", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-metrics-server-mocks", + "id": "def-server.sampleEsClientMetrics.averageIdleSocketsPerNode", + "type": "number", + "tags": [], + "label": "averageIdleSocketsPerNode", + "description": [], + "path": "packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ] }, diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index bcb29c53743c..3da8129abb0a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 7 | 0 | 7 | 0 | +| 19 | 0 | 19 | 0 | ## Server diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 530ca8138c75..cee2a79f8fac 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 0c5982822b6b..ae1b4654b8e8 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index ce0799c8a57e..83c7283e11de 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index ff9aa9bab3b1..9ae964ddb4e3 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 45e5581a7df1..8e7ad4d161b9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 4b5730a85899..474f6ab09bda 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index e0dc05465170..e1bd6b5ef59e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index b8f43851ff07..d07f78ffb9d5 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 3f88938a7d71..656ab7187bba 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index c0846ed65f58..dec4f69c482e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 70c9fbee9c49..a2418521db9c 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 4ebd8410d827..676f282449ef 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 96f2610b0eb1..9aad3e0b9310 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 876c2f5e25d9..39c0978cc6d0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 31744f9615bb..41988c7f87cc 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 000dd0d4cedb..b666f07c6369 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 7de298d84675..9d385203d541 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index bc6024dea788..19434ae87b70 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -1980,7 +1980,11 @@ "SavedObjectsFindOptionsReference", " | ", "SavedObjectsFindOptionsReference", - "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; preference?: string | undefined; }" + "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", + "SavedObjectsFindOptionsReference", + " | ", + "SavedObjectsFindOptionsReference", + "[] | undefined; hasNoReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; preference?: string | undefined; }" ], "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts", "deprecated": false, diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 51698a709634..c373b57c1273 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.devdocs.json b/api_docs/kbn_core_saved_objects_api_server.devdocs.json index 1bff25681bdb..cae43e5b22b0 100644 --- a/api_docs/kbn_core_saved_objects_api_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server.devdocs.json @@ -5207,6 +5207,53 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsFindOptions.hasNoReference", + "type": "CompoundType", + "tags": [], + "label": "hasNoReference", + "description": [ + "\nSearch for documents *not* having a reference to the specified objects.\nUse `hasNoReferenceOperator` to specify the operator to use when searching for multiple references." + ], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + " | ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + "[] | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-server.SavedObjectsFindOptions.hasNoReferenceOperator", + "type": "CompoundType", + "tags": [], + "label": "hasNoReferenceOperator", + "description": [ + "\nThe operator to use when searching by multiple references using the `hasNoReference` option. Defaults to `OR`" + ], + "signature": [ + "\"AND\" | \"OR\" | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-saved-objects-api-server", "id": "def-server.SavedObjectsFindOptions.defaultSearchOperator", @@ -6447,7 +6494,23 @@ "section": "def-server.SavedObjectsFindOptionsReference", "text": "SavedObjectsFindOptionsReference" }, - "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; typeToNamespacesMap?: Map | undefined; preference?: string | undefined; }" + "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; hasNoReference?: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + " | ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + "[] | undefined; hasNoReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; typeToNamespacesMap?: Map | undefined; preference?: string | undefined; }" ], "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts", "deprecated": false, diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index c8639a4b9c45..cff107daa711 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-10-04 +date: 2022-10-10 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 | |-------------------|-----------|------------------------|-----------------| -| 308 | 1 | 137 | 0 | +| 310 | 1 | 137 | 0 | ## Server 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 f14120830006..4890a7483790 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 6b0c824029fc..e76ae1fd05bb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 9e232227f556..2f47670abdfe 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 66a94ba86843..7415fc8f2401 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index eb654c74cca2..e72c29db3593 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index f077ed64b63e..16a8f9bb1f80 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 29da15ed698f..58f98e15e920 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index ac4e60624dcb..fcf15987f127 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index bebe1281e0ad..326c1611c791 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 4b0e97d192ea..07756a85c717 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index aed14d7466d2..10230a623220 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 8d8eb4b06ad7..536a73abac4a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 2a8902a1e518..0b8e044f0b57 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 9bd945299b18..74fbe52d0069 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 6a53d6086266..d266a38b89a2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 3747e6c41952..ebd8bbf47dc9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 093798e431c3..223043281e54 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 3232d2d6e809..639c80dd1618 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 6dc392f2f16c..4abe99291214 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index b2d57509adde..88d04e4eeea0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index d5e62a33ea07..5a3230d00fcb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 2341795744ee..d42b15ff5b04 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 1bdb4be978f3..60fd56639a01 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.devdocs.json b/api_docs/kbn_core_test_helpers_so_type_serializer.devdocs.json new file mode 100644 index 000000000000..10df0d4e5d2d --- /dev/null +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.devdocs.json @@ -0,0 +1,230 @@ +{ + "id": "@kbn/core-test-helpers-so-type-serializer", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.extractMigrationInfo", + "type": "Function", + "tags": [], + "label": "extractMigrationInfo", + "description": [ + "\nExtract all migration-relevant informations bound to given type in a serializable format.\n" + ], + "signature": [ + "(soType: ", + "SavedObjectsType", + ") => ", + { + "pluginId": "@kbn/core-test-helpers-so-type-serializer", + "scope": "server", + "docId": "kibKbnCoreTestHelpersSoTypeSerializerPluginApi", + "section": "def-server.SavedObjectTypeMigrationInfo", + "text": "SavedObjectTypeMigrationInfo" + } + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.extractMigrationInfo.$1", + "type": "Object", + "tags": [], + "label": "soType", + "description": [], + "signature": [ + "SavedObjectsType", + "" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.getMigrationHash", + "type": "Function", + "tags": [], + "label": "getMigrationHash", + "description": [], + "signature": [ + "(soType: ", + "SavedObjectsType", + ") => string" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/get_migration_hash.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.getMigrationHash.$1", + "type": "Object", + "tags": [], + "label": "soType", + "description": [], + "signature": [ + "SavedObjectsType", + "" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/get_migration_hash.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo", + "type": "Interface", + "tags": [], + "label": "SavedObjectTypeMigrationInfo", + "description": [], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo.namespaceType", + "type": "CompoundType", + "tags": [], + "label": "namespaceType", + "description": [], + "signature": [ + "\"single\" | \"multiple\" | \"multiple-isolated\" | \"agnostic\"" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo.convertToAliasScript", + "type": "string", + "tags": [], + "label": "convertToAliasScript", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo.convertToMultiNamespaceTypeVersion", + "type": "string", + "tags": [], + "label": "convertToMultiNamespaceTypeVersion", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo.migrationVersions", + "type": "Array", + "tags": [], + "label": "migrationVersions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo.schemaVersions", + "type": "Array", + "tags": [], + "label": "schemaVersions", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo.mappings", + "type": "Object", + "tags": [], + "label": "mappings", + "description": [], + "signature": [ + "{ [x: string]: unknown; }" + ], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-test-helpers-so-type-serializer", + "id": "def-server.SavedObjectTypeMigrationInfo.hasExcludeOnUpgrade", + "type": "boolean", + "tags": [], + "label": "hasExcludeOnUpgrade", + "description": [], + "path": "packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx new file mode 100644 index 000000000000..1d26649f21f5 --- /dev/null +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.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: kibKbnCoreTestHelpersSoTypeSerializerPluginApi +slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer +title: "@kbn/core-test-helpers-so-type-serializer" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin +date: 2022-10-10 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] +--- +import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 13 | 0 | 12 | 0 | + +## Server + +### Functions + + +### Interfaces + + diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 8c1171396e64..1b42667d99bb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 38293fb7bd7e..e69c72b770b7 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-10-04 +date: 2022-10-10 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 f0b9a5dcf855..f3b5cbe8e071 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index b4a0a55091a4..f7428dc79839 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 09b0a2dc5732..3489c2c081f4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 31d7e5da5da6..8f8093426e22 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 5b6eb33ecc40..c91d9beb9d48 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 812a5a98b620..634a41ec1fe5 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index b4098d7a6829..04d3804c3387 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 6350fd7ee506..e1878987d59e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 54fc17cd96f0..8bac77409ac0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 7722ffd3f68a..3761c0054f0d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 131c2f323331..c75090a9b614 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 6ca5ba080918..2b4f69204559 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 7da4515e5c5d..7c8b276e834c 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-10-04 +date: 2022-10-10 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 c0ab47b943fe..e0fd07d2de51 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-10-04 +date: 2022-10-10 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 81afcdef5fad..18d746039c14 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 1e541a024def..bcbe3e6f0708 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index c9e8aeb0ccee..582d465be5a6 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 83bf4e00c654..743a82b68b2e 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-10-04 +date: 2022-10-10 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 0cf3b152d866..fd01dbf8e550 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -300,7 +300,7 @@ "label": "enterpriseSearch", "description": [], "signature": [ - "{ readonly apiKeys: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; readonly connectorsMongoDB: string; readonly connectorsMySQL: string; readonly connectorsWorkplaceSearch: string; readonly contentExtraction: string; readonly crawlerGettingStarted: string; readonly crawlerManaging: string; readonly crawlerOverview: string; readonly documentLevelSecurity: string; readonly ingestPipelines: string; readonly languageAnalyzers: string; readonly languageClients: string; readonly licenseManagement: string; readonly mailService: string; readonly start: string; readonly troubleshootSetup: string; readonly usersAccess: string; }" + "{ readonly apiKeys: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; readonly connectorsMongoDB: string; readonly connectorsMySQL: string; readonly connectorsWorkplaceSearch: string; readonly contentExtraction: string; readonly crawlerGettingStarted: string; readonly crawlerManaging: string; readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; readonly ingestPipelines: string; readonly languageAnalyzers: string; readonly languageClients: string; readonly licenseManagement: string; readonly mailService: string; readonly start: string; readonly troubleshootSetup: string; readonly usersAccess: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, @@ -546,7 +546,7 @@ "label": "securitySolution", "description": [], "signature": [ - "{ readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; linux_deadlock: string; }; readonly packageActionTroubleshooting: { es_connection: string; }; readonly threatIntelInt: string; readonly responseActions: string; }" + "{ readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; linux_deadlock: string; }; readonly packageActionTroubleshooting: { es_connection: string; }; readonly threatIntelInt: string; readonly responseActions: string; readonly configureEndpointIntegrationPolicy: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, @@ -658,7 +658,7 @@ "label": "observability", "description": [], "signature": [ - "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly 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; }" + "{ 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; readonly syntheticsProjectMonitors: 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 f20c5c3e4621..5989572689e5 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index c92f20d4e82e..c95fc1e111f6 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-10-04 +date: 2022-10-10 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 f301c7fde5a8..177ceb72f72d 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-10-04 +date: 2022-10-10 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 1fd6cb7fc92b..5dca8c113383 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 0c2ba2f9d448..53ef00e7813c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.devdocs.json b/api_docs/kbn_es_query.devdocs.json index d84edf5ed865..3bd7db808950 100644 --- a/api_docs/kbn_es_query.devdocs.json +++ b/api_docs/kbn_es_query.devdocs.json @@ -765,6 +765,47 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.buildOrFilter", + "type": "Function", + "tags": [], + "label": "buildOrFilter", + "description": [ + "\nBuilds an OR filter. An OR filter is a filter with multiple sub-filters. Each sub-filter (FilterItem) represents a\ncondition." + ], + "signature": [ + "(filters: ", + "FilterItem", + "[]) => ", + "OrFilter" + ], + "path": "packages/kbn-es-query/src/filters/build_filters/or_filter.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.buildOrFilter.$1", + "type": "Array", + "tags": [], + "label": "filters", + "description": [ + "An array of OrFilterItem" + ], + "signature": [ + "FilterItem", + "[]" + ], + "path": "packages/kbn-es-query/src/filters/build_filters/or_filter.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es-query", "id": "def-common.buildPhraseFilter", @@ -1108,7 +1149,7 @@ "section": "def-common.DataViewBase", "text": "DataViewBase" }, - "[] | undefined, { ignoreFilterIfFieldNotInIndex, nestedIgnoreUnmapped }?: ", + "[] | undefined, options?: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -1186,7 +1227,7 @@ "id": "def-common.buildQueryFromFilters.$3", "type": "Object", "tags": [], - "label": "{ ignoreFilterIfFieldNotInIndex = false, nestedIgnoreUnmapped }", + "label": "options", "description": [], "signature": [ { @@ -2699,6 +2740,53 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.isOrFilter", + "type": "Function", + "tags": [], + "label": "isOrFilter", + "description": [], + "signature": [ + "(filter: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ") => boolean" + ], + "path": "packages/kbn-es-query/src/filters/build_filters/or_filter.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.isOrFilter.$1", + "type": "Object", + "tags": [], + "label": "filter", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + } + ], + "path": "packages/kbn-es-query/src/filters/build_filters/or_filter.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es-query", "id": "def-common.isPhraseFilter", diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 7b7bc93b5731..903a72f04b9b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 222 | 1 | 168 | 12 | +| 226 | 1 | 170 | 14 | ## Common diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index fe461a5b010f..de03102edaae 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index facb9ad74343..c32806046825 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index af6a78abd324..3b1d69004f2d 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-10-04 +date: 2022-10-10 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 81929ed2bb34..99c56f93be93 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 9c5b2b494ed5..1374a4aec1d9 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index cbe8ec573dfd..51789356f12d 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-10-04 +date: 2022-10-10 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 0958cd3c3ebf..aef3268c9bfb 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-10-04 +date: 2022-10-10 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 2ec267998795..66aba654ff77 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 7f42b8a468d7..4b7223d26561 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-10-04 +date: 2022-10-10 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 d4e8d54488ba..240a64c2264a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index a0b43f842438..cccab042144a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 7b9d043059c7..c9db829a85be 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-10-04 +date: 2022-10-10 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 d11c22a74ecd..fd6582ff8a7c 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-10-04 +date: 2022-10-10 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 39f9d65660d0..c6b3c5a349b3 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 19a46d0750ca..929f3442b07f 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-10-04 +date: 2022-10-10 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 7e5e0932af67..ea265ce6a91d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index f4c9aec83599..620cc2a26b48 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 2ab7c2cad23d..a407fb3d0067 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 4c78e0d66ea7..f8f1c0cbbf18 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 5cc97dbc4f9d..d9ca0522727a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 110b3b19a0fa..598854d6f14f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 11c646693dc0..7dd21a446f77 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-10-04 +date: 2022-10-10 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.mdx b/api_docs/kbn_ml_agg_utils.mdx index ccb0243714c5..02b13bbb589b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 9e756982a3c4..f7394b06d8d9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 5a08b38fdc36..0687bfe1ceb5 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 13902cebdc87..7eeb0479f984 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-10-04 +date: 2022-10-10 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 60b2e27c0fc5..8d45d987a956 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 805c9413ad27..10368217e8d4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index a2a0a8761979..dc1bc2010d83 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 66b4513e3e93..3fa082077bf0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index d0487d7546b1..fd1c4a622607 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index fe379e3b837c..91f107396120 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-10-04 +date: 2022-10-10 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 d1b5eb542c88..a2dc7b6bb5fb 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-10-04 +date: 2022-10-10 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 6d2caad78123..b4a5ffe67fbb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index 8db17c376261..513060301f14 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -844,6 +844,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_TIME_RANGE", + "type": "string", + "tags": [], + "label": "ALERT_TIME_RANGE", + "description": [], + "signature": [ + "\"kibana.alert.time_range\"" + ], + "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.ALERT_UUID", @@ -1077,7 +1092,7 @@ "label": "TechnicalRuleDataFieldName", "description": [], "signature": [ - "\"tags\" | \"kibana\" | \"@timestamp\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"event.action\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity\" | \"kibana.alert.status\" | \"kibana.version\" | \"ecs.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_user\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.system_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"event.kind\" | \"event.module\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert\" | \"kibana.alert.rule\"" + "\"tags\" | \"kibana\" | \"@timestamp\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"event.action\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity\" | \"kibana.alert.status\" | \"kibana.version\" | \"ecs.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_user\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.system_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"event.kind\" | \"event.module\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert\" | \"kibana.alert.rule\"" ], "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", "deprecated": false, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 2e861a2bd3a1..8842f16f9552 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 74 | 0 | 71 | 0 | +| 75 | 0 | 72 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 3d11d856ffe0..c1162de9f27a 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-10-04 +date: 2022-10-10 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 4ce360705125..55450f4d392a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index fe34878dde86..75ed0a758d7b 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2022-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index b7857cb66e9e..99d48e0e7c4c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 9f0b39175cdd..47e94eafc034 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index bf6b60393ec5..0efde1f2fc5e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index ccbb90b45095..879dab8d1986 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 2b3c7663b879..d5f0dd0f088f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index a5e08ee825fa..eb7870fb2781 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 7b169ce42760..a72acb71f834 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 5c977abb1b28..818f3cedcf43 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.devdocs.json b/api_docs/kbn_securitysolution_list_utils.devdocs.json index 0518a331879b..39f11e812f58 100644 --- a/api_docs/kbn_securitysolution_list_utils.devdocs.json +++ b/api_docs/kbn_securitysolution_list_utils.devdocs.json @@ -1514,7 +1514,7 @@ "label": "getNewExceptionItem", "description": [], "signature": [ - "({ listId, namespaceType, ruleName, }: { listId: string; namespaceType: \"single\" | \"agnostic\"; ruleName: string; }) => ", + "({ listId, namespaceType, ruleName, }: { listId: string | undefined; namespaceType: \"single\" | \"agnostic\" | undefined; ruleName: string; }) => ", { "pluginId": "@kbn/securitysolution-list-utils", "scope": "common", @@ -1545,6 +1545,9 @@ "tags": [], "label": "listId", "description": [], + "signature": [ + "string | undefined" + ], "path": "packages/kbn-securitysolution-list-utils/src/helpers/index.ts", "deprecated": false, "trackAdoption": false @@ -1557,7 +1560,7 @@ "label": "namespaceType", "description": [], "signature": [ - "\"single\" | \"agnostic\"" + "\"single\" | \"agnostic\" | undefined" ], "path": "packages/kbn-securitysolution-list-utils/src/helpers/index.ts", "deprecated": false, @@ -2597,7 +2600,7 @@ "label": "CreateExceptionListItemBuilderSchema", "description": [], "signature": [ - "Omit<{ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; list_id: string; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; meta?: object | undefined; namespace_type?: \"single\" | \"agnostic\" | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; }, \"meta\" | \"entries\"> & { meta: { temporaryUuid: string; }; entries: ", + "Omit<{ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; list_id: string; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; meta?: object | undefined; namespace_type?: \"single\" | \"agnostic\" | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; }, \"meta\" | \"entries\" | \"list_id\" | \"namespace_type\"> & { meta: { temporaryUuid: string; }; entries: ", { "pluginId": "@kbn/securitysolution-list-utils", "scope": "common", @@ -2605,7 +2608,7 @@ "section": "def-common.BuilderEntry", "text": "BuilderEntry" }, - "[]; }" + "[]; list_id: string | undefined; namespace_type: \"single\" | \"agnostic\" | undefined; }" ], "path": "packages/kbn-securitysolution-list-utils/src/types/index.ts", "deprecated": false, @@ -2782,6 +2785,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/securitysolution-list-utils", + "id": "def-common.ExceptionsBuilderReturnExceptionItem", + "type": "Type", + "tags": [], + "label": "ExceptionsBuilderReturnExceptionItem", + "description": [], + "signature": [ + "{ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; } | ({ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; list_id: string; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; meta?: object | undefined; namespace_type?: \"single\" | \"agnostic\" | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; }) | ({ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; list_id?: undefined; meta?: object | undefined; namespace_type?: \"single\" | \"agnostic\" | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; })" + ], + "path": "packages/kbn-securitysolution-list-utils/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/securitysolution-list-utils", "id": "def-common.SavedObjectType", diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 768c6cf059b5..d3b238f064f0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 191 | 0 | 149 | 0 | +| 192 | 0 | 150 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 3496eef3b634..9819937553c2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 2d25ede3c66e..3fe4182737cb 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 26c6aa46ba08..cffd24280bd8 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 16eac1230f6d..63492dc74b4a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 675a4f7818c6..f129163c988e 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-10-04 +date: 2022-10-10 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 bda0bfc9746f..44d456487ad0 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-10-04 +date: 2022-10-10 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 23abaffe3cc7..01725450e1a3 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-10-04 +date: 2022-10-10 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 e553b3a2c6c5..1ca4cca27586 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-10-04 +date: 2022-10-10 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 6465028feeb1..54896916668a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index c570628e0e33..0c858f6846b3 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 82c15083cc99..78c646f343f8 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 56afa671aa65..fe9810474613 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 557e698f2e51..2d756c72ea7a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 352a0014b2cc..a8ac95588218 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index b28052e80a11..7926794780d4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 15a591c9aecd..5c8075601d9a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 4c476350996e..df0736b573f1 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 2e4fc8779860..39ee413fc767 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index f22caeb920fc..edab8ba9cded 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 0f95fd61f257..3238e35f522a 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 80f6cf9b8f34..d91e3c960d34 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index fadea535b824..c908ad8566ed 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index aa9686f1314b..19fa427b65ad 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 95295947ea4c..039d9ad79e77 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index a136219cf2d8..691755b31bb3 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index cbcc71d025c8..838f60da629e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 40896d72b0dc..753b087e6f37 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 133cbe3bb999..c2935ffb7d51 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 443625c76310..0d2b31cecba0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 118290662173..e73b81401efa 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index ea558aa9e073..d5317c0029e9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index e3a9c03528b2..c93ed16d6f15 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-10-04 +date: 2022-10-10 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 e5c5d241941b..07dcb1a3659d 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 1d5e0e379258..2e32807ed0a2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 49592c87fc21..04a276c4eb96 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-10-04 +date: 2022-10-10 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 548085afb06a..bfa6f898f430 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 130c68213d1d..bb0dc9dcf816 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index c9b070d08edf..a1e8dbf164f4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index e93c241e2cda..393f75434d83 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index da5f83dd7a47..3e90ec8fb19e 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-10-04 +date: 2022-10-10 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 9ce0e8072dec..1b1c168133c5 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-10-04 +date: 2022-10-10 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 d6c96609eee4..8d1abc35c23d 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-10-04 +date: 2022-10-10 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 365d565746db..d108fa2cf7a0 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-10-04 +date: 2022-10-10 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.mdx b/api_docs/kbn_ui_theme.mdx index d8a381bf8268..c0ec429464c4 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-10-04 +date: 2022-10-10 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 1fb9abaf0ee8..b0967bc0c569 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 1dd233e2b3c7..418262e39279 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index ffc882e7050c..662803563e24 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 32d4e0b6f4f2..0d1e45293637 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-10-04 +date: 2022-10-10 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 8321d021b072..83b89a98d54b 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-10-04 +date: 2022-10-10 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 6ea90bb2df76..6f9e09103095 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 1a43fddabf34..8ea2cb868f74 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 7658f6b8dfa6..c35811e02734 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index f51fe23a0772..fa1459a1b6c1 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-10-04 +date: 2022-10-10 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 d29c21bc24d3..6749b467d5b1 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -10805,39 +10805,6 @@ ], "returnComment": [], "initialIsOpen": false - }, - { - "parentPluginId": "lens", - "id": "def-common.isPartitionShape", - "type": "Function", - "tags": [], - "label": "isPartitionShape", - "description": [], - "signature": [ - "(shape: string) => boolean" - ], - "path": "x-pack/plugins/lens/common/visualizations/partition/utils.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "lens", - "id": "def-common.isPartitionShape.$1", - "type": "string", - "tags": [], - "label": "shape", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/lens/common/visualizations/partition/utils.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false } ], "interfaces": [ diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index e930504ffaee..93d38cc9b09a 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-10-04 +date: 2022-10-10 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 | |-------------------|-----------|------------------------|-----------------| -| 658 | 0 | 567 | 45 | +| 656 | 0 | 565 | 45 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 14b1e5c6f461..44640af1df06 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index b71a0f65fbba..c3a53ddfcb76 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 914e71d3130c..05f7e0d8fd34 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.devdocs.json b/api_docs/lists.devdocs.json index 40c17f8b9f3a..83be2ee9706c 100644 --- a/api_docs/lists.devdocs.json +++ b/api_docs/lists.devdocs.json @@ -288,7 +288,8 @@ "label": "exceptionItems", "description": [], "signature": [ - "({ _version: string | undefined; comments: ({ comment: string; created_at: string; created_by: string; id: string; } & { updated_at?: string | undefined; updated_by?: string | undefined; })[]; created_at: string; created_by: string; description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; })[]; id: string; item_id: string; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"simple\"; updated_at: string; updated_by: string; } | ({ description: string; entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"wildcard\"; value: string; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; })[]; field: string; type: \"nested\"; })[]; list_id: string; name: string; type: \"simple\"; } & { comments?: { comment: string; }[] | undefined; item_id?: string | undefined; meta?: object | undefined; namespace_type?: \"single\" | \"agnostic\" | undefined; os_types?: (\"windows\" | \"linux\" | \"macos\")[] | undefined; tags?: string[] | undefined; }))[]" + "ExceptionsBuilderReturnExceptionItem", + "[]" ], "path": "x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx", "deprecated": false, diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 014535d3acb8..fb83ae6f81a3 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-10-04 +date: 2022-10-10 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 de79c0505493..0393eb71f363 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.devdocs.json b/api_docs/maps.devdocs.json index 83836732b0bf..d40b99e5b579 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -2182,6 +2182,149 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "maps", + "id": "def-public.IRasterSource", + "type": "Interface", + "tags": [], + "label": "IRasterSource", + "description": [], + "signature": [ + { + "pluginId": "maps", + "scope": "public", + "docId": "kibMapsPluginApi", + "section": "def-public.IRasterSource", + "text": "IRasterSource" + }, + " extends ", + { + "pluginId": "maps", + "scope": "public", + "docId": "kibMapsPluginApi", + "section": "def-public.ITMSSource", + "text": "ITMSSource" + } + ], + "path": "x-pack/plugins/maps/public/classes/sources/raster_source/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "maps", + "id": "def-public.IRasterSource.canSkipSourceUpdate", + "type": "Function", + "tags": [], + "label": "canSkipSourceUpdate", + "description": [], + "signature": [ + "(dataRequest: ", + { + "pluginId": "maps", + "scope": "public", + "docId": "kibMapsPluginApi", + "section": "def-public.DataRequest", + "text": "DataRequest" + }, + ", nextRequestMeta: ", + "DataRequestMeta", + ") => Promise" + ], + "path": "x-pack/plugins/maps/public/classes/sources/raster_source/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "maps", + "id": "def-public.IRasterSource.canSkipSourceUpdate.$1", + "type": "Object", + "tags": [], + "label": "dataRequest", + "description": [], + "signature": [ + { + "pluginId": "maps", + "scope": "public", + "docId": "kibMapsPluginApi", + "section": "def-public.DataRequest", + "text": "DataRequest" + } + ], + "path": "x-pack/plugins/maps/public/classes/sources/raster_source/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "maps", + "id": "def-public.IRasterSource.canSkipSourceUpdate.$2", + "type": "CompoundType", + "tags": [], + "label": "nextRequestMeta", + "description": [], + "signature": [ + "DataRequestMeta" + ], + "path": "x-pack/plugins/maps/public/classes/sources/raster_source/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "maps", + "id": "def-public.IRasterSource.isSourceStale", + "type": "Function", + "tags": [], + "label": "isSourceStale", + "description": [], + "signature": [ + "(mbSource: maplibregl.RasterTileSource, sourceData: ", + "RasterTileSourceData", + ") => boolean" + ], + "path": "x-pack/plugins/maps/public/classes/sources/raster_source/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "maps", + "id": "def-public.IRasterSource.isSourceStale.$1", + "type": "Object", + "tags": [], + "label": "mbSource", + "description": [], + "signature": [ + "maplibregl.RasterTileSource" + ], + "path": "x-pack/plugins/maps/public/classes/sources/raster_source/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "maps", + "id": "def-public.IRasterSource.isSourceStale.$2", + "type": "Object", + "tags": [], + "label": "sourceData", + "description": [], + "signature": [ + "RasterTileSourceData" + ], + "path": "x-pack/plugins/maps/public/classes/sources/raster_source/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "maps", "id": "def-public.ITMSSource", diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 0c1d38860285..9816101695ae 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; @@ -21,7 +21,7 @@ Contact [GIS](https://github.com/orgs/elastic/teams/kibana-gis) for questions re | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 256 | 0 | 255 | 25 | +| 263 | 0 | 262 | 26 | ## Client diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 82536b7c87b8..fe95c1e9304b 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-10-04 +date: 2022-10-10 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 9d94398a882d..55d7ef71b1a2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index c76aac1bd57a..da7874274db4 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-10-04 +date: 2022-10-10 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 e59f472f07ee..2f60b591347c 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-10-04 +date: 2022-10-10 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 0dccfeb623a3..28aac147b57e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 08394f81d45d..fecad3b64301 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-10-04 +date: 2022-10-10 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 08f23a62393d..2c0803499507 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -738,7 +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\" | \"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\" | \"alerts\" | \"paddingSize\" | \"focusTrapProps\" | \"ownFocus\" | \"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; }" + " & { 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, @@ -3793,7 +3793,7 @@ "label": "format", "description": [], "signature": [ - "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -3812,7 +3812,7 @@ "label": "options", "description": [], "signature": [ - "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -5320,7 +5320,7 @@ "label": "ObservabilityRuleTypeFormatter", "description": [], "signature": [ - "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -5339,7 +5339,7 @@ "label": "options", "description": [], "signature": [ - "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -7759,7 +7759,129 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; } & { name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: true; }; budgeting_method: \"occurrences\"; objective: { target: number; }; }, ", + ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; }; budgeting_method: \"occurrences\"; objective: { target: number; }; revision: number; created_at: Date; updated_at: Date; }, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "> | undefined; \"PUT /api/observability/slos/{id}\"?: ", + "ServerRoute", + "<\"PUT /api/observability/slos/{id}\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; body: ", + "PartialC", + "<{ name: ", + "StringC", + "; description: ", + "StringC", + "; indicator: ", + "UnionC", + "<[", + "TypeC", + "<{ type: ", + "LiteralC", + "; params: ", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; service: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; transaction_type: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; transaction_name: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; 'threshold.us': ", + "NumberC", + "; }>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; service: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; transaction_type: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; transaction_name: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; }>, ", + "PartialC", + "<{ good_status_codes: ", + "ArrayC", + "<", + "UnionC", + "<[", + "LiteralC", + "<\"2xx\">, ", + "LiteralC", + "<\"3xx\">, ", + "LiteralC", + "<\"4xx\">, ", + "LiteralC", + "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "TypeC", + "<{ duration: ", + "StringC", + "; is_rolling: ", + "LiteralC", + "; }>; budgeting_method: ", + "LiteralC", + "<\"occurrences\">; objective: ", + "TypeC", + "<{ target: ", + "NumberC", + "; }>; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; }; budgeting_method: \"occurrences\"; objective: { target: number; }; created_at: Date; updated_at: Date; }, ", { "pluginId": "observability", "scope": "server", @@ -7783,7 +7905,7 @@ "TypeC", "<{ type: ", "LiteralC", - "<\"slo.apm.transaction_duration\">; params: ", + "; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -7815,7 +7937,7 @@ "TypeC", "<{ type: ", "LiteralC", - "<\"slo.apm.transaction_error_rate\">; params: ", + "; params: ", "IntersectionC", "<[", "TypeC", @@ -7863,7 +7985,7 @@ "StringC", "; is_rolling: ", "LiteralC", - "; }>; budgeting_method: ", + "; }>; budgeting_method: ", "LiteralC", "<\"occurrences\">; objective: ", "TypeC", @@ -7987,7 +8109,129 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; } & { name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: true; }; budgeting_method: \"occurrences\"; objective: { target: number; }; }, ", + ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; }; budgeting_method: \"occurrences\"; objective: { target: number; }; revision: number; created_at: Date; updated_at: Date; }, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "> | undefined; \"PUT /api/observability/slos/{id}\"?: ", + "ServerRoute", + "<\"PUT /api/observability/slos/{id}\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; body: ", + "PartialC", + "<{ name: ", + "StringC", + "; description: ", + "StringC", + "; indicator: ", + "UnionC", + "<[", + "TypeC", + "<{ type: ", + "LiteralC", + "; params: ", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; service: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; transaction_type: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; transaction_name: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; 'threshold.us': ", + "NumberC", + "; }>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "; params: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; service: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; transaction_type: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; transaction_name: ", + "UnionC", + "<[", + "LiteralC", + "<\"*\">, ", + "StringC", + "]>; }>, ", + "PartialC", + "<{ good_status_codes: ", + "ArrayC", + "<", + "UnionC", + "<[", + "LiteralC", + "<\"2xx\">, ", + "LiteralC", + "<\"3xx\">, ", + "LiteralC", + "<\"4xx\">, ", + "LiteralC", + "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "TypeC", + "<{ duration: ", + "StringC", + "; is_rolling: ", + "LiteralC", + "; }>; budgeting_method: ", + "LiteralC", + "<\"occurrences\">; objective: ", + "TypeC", + "<{ target: ", + "NumberC", + "; }>; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; }; budgeting_method: \"occurrences\"; objective: { target: number; }; created_at: Date; updated_at: Date; }, ", { "pluginId": "observability", "scope": "server", @@ -8011,7 +8255,7 @@ "TypeC", "<{ type: ", "LiteralC", - "<\"slo.apm.transaction_duration\">; params: ", + "; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -8043,7 +8287,7 @@ "TypeC", "<{ type: ", "LiteralC", - "<\"slo.apm.transaction_error_rate\">; params: ", + "; params: ", "IntersectionC", "<[", "TypeC", @@ -8091,7 +8335,7 @@ "StringC", "; is_rolling: ", "LiteralC", - "; }>; budgeting_method: ", + "; }>; budgeting_method: ", "LiteralC", "<\"occurrences\">; objective: ", "TypeC", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index aaa79beceb7a..a3c878a3cf2b 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index e79c8673975d..e6cbfa7500c9 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 655626fbdb65..020ecc70d421 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,40 +15,43 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 480 | 399 | 38 | +| 487 | 404 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 32043 | 179 | 21580 | 1010 | +| 32173 | 179 | 21657 | 1023 | ## Plugin Directory | Plugin name           | Maintaining team | Description | API Cnt | Any Cnt | Missing
comments | Missing
exports | |--------------|----------------|-----------|--------------|----------|---------------|--------| -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 214 | 0 | 209 | 19 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 213 | 0 | 208 | 23 | | | [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) | - | 379 | 0 | 370 | 24 | | | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 52 | | | [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 | +| | [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. | 81 | 1 | 72 | 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 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 34 | 0 | 26 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | Chat available on Elastic Cloud deployments for quicker assistance. | 1 | 0 | 0 | 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. | 12 | 0 | 0 | 0 | +| cloudFullStory | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | When Kibana runs on Elastic Cloud, this plugin registers FullStory as a shipper for telemetry. | 0 | 0 | 0 | 0 | +| cloudLinks | [Kibana Core](https://github.com/orgs/elastic/teams/@kibana-core) | Adds the links to the Elastic Cloud console | 0 | 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) | - | 2686 | 0 | 29 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2689 | 0 | 23 | 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 | 104 | 0 | 85 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 120 | 0 | 113 | 3 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 52 | 0 | 51 | 0 | -| | [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. | 3213 | 33 | 2509 | 23 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 15 | 0 | 7 | 0 | +| | [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. | 3221 | 33 | 2513 | 24 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 16 | 0 | 7 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Reusable data view field editor across Kibana | 60 | 0 | 30 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data view management app | 2 | 0 | 2 | 0 | | | [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. | 983 | 0 | 225 | 2 | @@ -67,7 +70,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Gauge plugin adds a `gauge` renderer and function to the expression plugin. The renderer will display the `gauge` chart. | 57 | 0 | 57 | 2 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart. | 105 | 0 | 101 | 3 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'image' function and renderer to expressions | 26 | 0 | 26 | 0 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Adds a `metric` renderer and function to the expression plugin. The renderer will display the `legacy metric` chart. | 48 | 0 | 48 | 1 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Adds a `metric` renderer and function to the expression plugin. The renderer will display the `legacy metric` chart. | 49 | 0 | 49 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'metric' function and renderer to expressions | 32 | 0 | 27 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Adds a `metric` renderer and function to the expression plugin. The renderer will display the `metric` chart. | 56 | 0 | 56 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Partition Visualization plugin adds a `partitionVis` renderer and `pieVis`, `mosaicVis`, `treemapVis`, `waffleVis` functions to the expression plugin. The renderer will display the `pie`, `waffle`, `treemap` and `mosaic` charts. | 70 | 0 | 70 | 2 | @@ -80,8 +83,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. | 263 | 0 | 14 | 2 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 997 | 3 | 893 | 17 | +| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 270 | 0 | 15 | 2 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 996 | 3 | 893 | 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 | @@ -101,14 +104,14 @@ 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. | 658 | 0 | 567 | 45 | +| | [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. | 656 | 0 | 565 | 45 | | | [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 | | | [Security detections response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 204 | 0 | 92 | 50 | | logstash | [Logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 41 | 0 | 41 | 6 | -| | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 256 | 0 | 255 | 25 | +| | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 263 | 0 | 262 | 26 | | | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 67 | 0 | 67 | 0 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 251 | 9 | 78 | 39 | | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 11 | 0 | 9 | 1 | @@ -134,7 +137,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 32 | 0 | 13 | 0 | | | [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. | 250 | 0 | 90 | 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. | 249 | 0 | 90 | 0 | | | [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 | @@ -152,11 +155,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 452 | 1 | 346 | 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) | - | 512 | 1 | 485 | 48 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 517 | 1 | 489 | 49 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 133 | 0 | 92 | 11 | | | [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. | 128 | 2 | 102 | 17 | +| | [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. | 131 | 2 | 104 | 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 | @@ -175,7 +178,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. | 725 | 12 | 695 | 18 | +| | [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. | 729 | 12 | 699 | 18 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory @@ -240,8 +243,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 4 | 0 | 4 | 0 | | | Kibana Core | - | 5 | 0 | 2 | 0 | | | Kibana Core | - | 4 | 0 | 4 | 0 | -| | Kibana Core | - | 13 | 0 | 11 | 1 | -| | Kibana Core | - | 36 | 1 | 32 | 0 | +| | Kibana Core | - | 15 | 0 | 13 | 2 | +| | Kibana Core | - | 38 | 1 | 34 | 0 | | | Kibana Core | - | 99 | 0 | 51 | 0 | | | Kibana Core | - | 33 | 0 | 29 | 0 | | | Kibana Core | - | 15 | 1 | 15 | 0 | @@ -262,6 +265,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 4 | 0 | 0 | 0 | | | Kibana Core | - | 10 | 1 | 10 | 0 | | | Kibana Core | - | 14 | 0 | 11 | 0 | +| | [Owner missing] | - | 20 | 0 | 6 | 0 | +| | [Owner missing] | - | 9 | 0 | 9 | 3 | +| | Kibana Core | - | 7 | 0 | 7 | 0 | | | Kibana Core | - | 25 | 5 | 25 | 1 | | | Kibana Core | - | 7 | 0 | 7 | 1 | | | Kibana Core | - | 392 | 1 | 154 | 0 | @@ -281,11 +287,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 56 | 0 | 30 | 0 | | | Kibana Core | - | 9 | 0 | 5 | 1 | | | Kibana Core | - | 13 | 0 | 12 | 0 | -| | Kibana Core | - | 24 | 0 | 20 | 0 | +| | Kibana Core | - | 29 | 0 | 25 | 0 | | | Kibana Core | - | 11 | 1 | 11 | 0 | -| | Kibana Core | - | 48 | 0 | 8 | 0 | -| | Kibana Core | - | 5 | 0 | 5 | 0 | -| | Kibana Core | - | 7 | 0 | 7 | 0 | +| | Kibana Core | - | 62 | 0 | 8 | 0 | +| | Kibana Core | - | 6 | 0 | 6 | 0 | +| | Kibana Core | - | 19 | 0 | 19 | 0 | | | Kibana Core | - | 6 | 0 | 0 | 0 | | | Kibana Core | - | 5 | 0 | 0 | 0 | | | Kibana Core | - | 3 | 0 | 3 | 0 | @@ -304,7 +310,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 2 | 0 | 2 | 0 | | | Kibana Core | - | 4 | 0 | 4 | 1 | | | Kibana Core | - | 106 | 1 | 75 | 0 | -| | Kibana Core | - | 308 | 1 | 137 | 0 | +| | Kibana Core | - | 310 | 1 | 137 | 0 | | | Kibana Core | - | 71 | 0 | 51 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | | | Kibana Core | - | 37 | 0 | 31 | 1 | @@ -328,6 +334,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 4 | 0 | 4 | 0 | | | Kibana Core | - | 11 | 0 | 9 | 0 | | | Kibana Core | - | 5 | 0 | 5 | 0 | +| | Kibana Core | - | 13 | 0 | 12 | 0 | | | Kibana Core | - | 6 | 0 | 4 | 0 | | | Kibana Core | - | 2 | 0 | 1 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | @@ -353,7 +360,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 19 | 0 | 11 | 0 | | | [Owner missing] | - | 27 | 0 | 14 | 1 | | | Kibana Core | - | 7 | 0 | 3 | 0 | -| | [Owner missing] | - | 222 | 1 | 168 | 12 | +| | [Owner missing] | - | 226 | 1 | 170 | 14 | | | Kibana Core | - | 11 | 0 | 11 | 0 | | | [Owner missing] | - | 2 | 0 | 1 | 0 | | | [Owner missing] | - | 20 | 0 | 16 | 0 | @@ -388,7 +395,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | Just some helpers for kibana plugin devs. | 1 | 0 | 1 | 0 | | | [Owner missing] | - | 21 | 0 | 10 | 0 | | | [Owner missing] | - | 6 | 0 | 6 | 1 | -| | [Owner missing] | - | 74 | 0 | 71 | 0 | +| | [Owner missing] | - | 75 | 0 | 72 | 0 | | | [Owner missing] | Security Solution auto complete | 56 | 1 | 41 | 1 | | | [Owner missing] | security solution elastic search utilities to use across plugins such lists, security_solution, cases, etc... | 67 | 0 | 61 | 1 | | | [Owner missing] | - | 76 | 0 | 67 | 1 | @@ -400,7 +407,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | security solution list REST API | 67 | 0 | 64 | 0 | | | [Owner missing] | security solution list constants to use across plugins such lists, security_solution, cases, etc... | 33 | 0 | 17 | 0 | | | [Owner missing] | Security solution list ReactJS hooks | 58 | 0 | 47 | 0 | -| | [Owner missing] | security solution list utilities | 191 | 0 | 149 | 0 | +| | [Owner missing] | security solution list utilities | 192 | 0 | 150 | 0 | | | [Owner missing] | security solution rule utilities to use across plugins | 26 | 0 | 23 | 0 | | | [Owner missing] | security solution t-grid packages will allow sharing components between timelines and security_solution plugin until we transfer all functionality to timelines plugin | 120 | 0 | 116 | 0 | | | [Owner missing] | security solution utilities to use across plugins such lists, security_solution, cases, etc... | 31 | 0 | 29 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 7e399db93218..72ab2b6b9b0c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.devdocs.json b/api_docs/profiling.devdocs.json index 1b5e38396b43..59280e0c4a8a 100644 --- a/api_docs/profiling.devdocs.json +++ b/api_docs/profiling.devdocs.json @@ -88,7 +88,7 @@ "label": "getRoutePaths", "description": [], "signature": [ - "() => { TopN: string; TopNContainers: string; TopNDeployments: string; TopNFunctions: string; TopNHosts: string; TopNThreads: string; TopNTraces: string; Flamechart: string; FrameInformation: string; }" + "() => { TopN: string; TopNContainers: string; TopNDeployments: string; TopNFunctions: string; TopNHosts: string; TopNThreads: string; TopNTraces: string; Flamechart: string; }" ], "path": "x-pack/plugins/profiling/common/index.ts", "deprecated": false, diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 7efed272b726..c374d8ff9bb2 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-10-04 +date: 2022-10-10 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 f59d7c397918..2cd2b8b54502 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index dbd1e74d06cd..25fde92c9c02 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 397ea6ae4dc9..a7ce02fa445a 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-10-04 +date: 2022-10-10 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 089f973bc0bf..37e78ced1236 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -63,7 +63,7 @@ "label": "get", "description": [], "signature": [ - "({ id, index }: GetAlertParams) => Promise> | undefined>" + "({ id, index }: GetAlertParams) => Promise> | undefined>" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", "deprecated": false, @@ -203,7 +203,7 @@ "SortOptions", "[] | undefined; search_after?: (string | number)[] | undefined; }) => Promise<", "SearchResponse", - ">, unknown>>" + ">, unknown>>" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", "deprecated": false, @@ -2319,7 +2319,7 @@ "SearchRequest", ">(request: TSearchRequest) => Promise<", "ESSearchResponse", - "> & OutputOf>>, TSearchRequest, { restTotalHitsAsInt: false; }>>" + "> & OutputOf>>, TSearchRequest, { restTotalHitsAsInt: false; }>>" ], "path": "x-pack/plugins/rule_registry/server/rule_data_client/types.ts", "deprecated": false, @@ -3949,7 +3949,7 @@ "label": "parseTechnicalFields", "description": [], "signature": [ - "(input: unknown, partial?: boolean) => OutputOf>" + "(input: unknown, partial?: boolean) => OutputOf>" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, @@ -4270,7 +4270,7 @@ "label": "ParsedTechnicalFields", "description": [], "signature": [ - "{ readonly '@timestamp': string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.space_ids\": string[]; readonly \"kibana.alert.uuid\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.rule.name\": string; readonly tags?: string[] | undefined; readonly 'event.action'?: string | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly 'event.kind'?: string | undefined; }" + "{ readonly '@timestamp': string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.space_ids\": string[]; readonly \"kibana.alert.uuid\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.rule.name\": string; readonly tags?: string[] | undefined; readonly 'event.action'?: string | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly 'event.kind'?: string | undefined; }" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index f48194ae5bd1..36968e9f6d84 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 2be7beb68c70..cfd864e5e1e7 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index d582458137cf..3ddfe47eb5d2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 4e6d34158bb8..f947e389666e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 7722b187a54d..092940749757 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index c112fd930e4d..6e3788871ed7 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 5299c1ca0eac..b0006411b10c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 06e4379d4afd..4694eea70cad 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index fda8615811c4..800a8386708e 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index f4735a3d7a26..e3e29cd30335 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index 1b69dcaad695..b913d81e4e55 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -2549,10 +2549,6 @@ "plugin": "data", "path": "src/plugins/data/server/search/session/session_service.ts" }, - { - "plugin": "cloud", - "path": "x-pack/plugins/cloud/server/routes/chat.ts" - }, { "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/annotations.ts" @@ -2589,6 +2585,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts" }, + { + "plugin": "cloudChat", + "path": "x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts" @@ -2720,24 +2720,6 @@ "path": "x-pack/plugins/security/server/plugin.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "security", - "id": "def-server.SecurityPluginSetup.setIsElasticCloudDeployment", - "type": "Function", - "tags": [], - "label": "setIsElasticCloudDeployment", - "description": [ - "\nSets the flag to indicate that Kibana is running inside an Elastic Cloud deployment. This flag is supposed to be\nset by the Cloud plugin and can be only once." - ], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/security/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] } ], "lifecycle": "setup", diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 2800db424ba6..5fe2fe589425 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Platform Security](https://github.com/orgs/elastic/teams/kibana-securit | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 250 | 0 | 90 | 0 | +| 249 | 0 | 90 | 0 | ## Client diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 3ddca540a2d1..33d50cb8babe 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index e54037feb355..77ba39d41f48 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 976c243de639..df0619d9b529 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index f83be535a94b..1c86c9c84811 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 7b751efc3d1d..04356137a093 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-10-04 +date: 2022-10-10 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 3850659e8a4d..9614f1ebea57 100644 --- a/api_docs/stack_alerts.devdocs.json +++ b/api_docs/stack_alerts.devdocs.json @@ -24,7 +24,7 @@ "signature": [ "\".index-threshold\"" ], - "path": "x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts", + "path": "x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 7c124256a797..909167b8d55c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 7bf19fb25c36..6c4965073257 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 3cabbc8a8382..1217311901de 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index e3220b9e593a..bcedaaf0d81f 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 03d8bceec41f..5d0c3054de25 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index a3721f57358d..e0bc1e797caa 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 5c29d491cf1d..b352bd4e0dca 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-10-04 +date: 2022-10-10 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 5834a118d4c1..77d1f454f2f5 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index f33b1267f8f4..a010695c5c77 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 3237f674591f..4d851dc086fe 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-10-04 +date: 2022-10-10 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 d6351e414ae4..e9e5908c5ee2 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -3139,7 +3139,7 @@ "description": [], "signature": [ "BasicFields", - " & { tags?: string[] | undefined; kibana?: string[] | undefined; \"@timestamp\"?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"event.action\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; } & { [x: string]: unknown[]; }" + " & { tags?: string[] | undefined; kibana?: string[] | undefined; \"@timestamp\"?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"event.action\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.time_range\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; } & { [x: string]: unknown[]; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, @@ -3942,6 +3942,22 @@ "path": "x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.FieldBrowserOptions.preselectedCategoryIds", + "type": "Array", + "tags": [], + "label": "preselectedCategoryIds", + "description": [ + "\nCategories that should be selected initially" + ], + "signature": [ + "string[] | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -8025,6 +8041,42 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUIPublicPluginStart.getRuleSnoozeModal", + "type": "Function", + "tags": [], + "label": "getRuleSnoozeModal", + "description": [], + "signature": [ + "(props: ", + "RuleSnoozeModalProps", + ") => React.ReactElement<", + "RuleSnoozeModalProps", + ", string | React.JSXElementConstructor>" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUIPublicPluginStart.getRuleSnoozeModal.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "RuleSnoozeModalProps" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "lifecycle": "start", @@ -8164,6 +8216,18 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-server.TIME_SERIES_BUCKET_SELECTOR_FIELD", + "type": "string", + "tags": [], + "label": "TIME_SERIES_BUCKET_SELECTOR_FIELD", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-server.TimeSeriesQuery", @@ -8545,6 +8609,17 @@ "path": "x-pack/plugins/triggers_actions_ui/common/data/index.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-common.TimeSeriesResult.truncated", + "type": "boolean", + "tags": [], + "label": "truncated", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/common/data/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 5146401d1a6e..a4ab7aaa8680 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-10-04 +date: 2022-10-10 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 | |-------------------|-----------|------------------------|-----------------| -| 512 | 1 | 485 | 48 | +| 517 | 1 | 489 | 49 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 3997d6297067..ba35eb86cbf0 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 0644b977bc92..45bddb4318d6 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index bfb157318d95..3b29a19fa6be 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-10-04 +date: 2022-10-10 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 5678db92f00c..69a6b1dece26 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -11,7 +11,7 @@ "label": "DataViewPicker", "description": [], "signature": [ - "({ isMissingCurrent, currentDataViewId, adHocDataViews, onChangeDataView, onAddField, onDataViewCreated, trigger, selectableProps, textBasedLanguages, onSaveTextLanguageQuery, onTextLangQuerySubmit, textBasedLanguage, onCreateDefaultAdHocDataView, isDisabled, }: ", + "({ isMissingCurrent, currentDataViewId, adHocDataViews, onChangeDataView, onEditDataView, onAddField, onDataViewCreated, trigger, selectableProps, textBasedLanguages, onSaveTextLanguageQuery, onTextLangQuerySubmit, textBasedLanguage, onCreateDefaultAdHocDataView, isDisabled, }: ", "DataViewPickerPropsExtended", ") => JSX.Element" ], @@ -24,7 +24,7 @@ "id": "def-public.DataViewPicker.$1", "type": "Object", "tags": [], - "label": "{\n isMissingCurrent,\n currentDataViewId,\n adHocDataViews,\n onChangeDataView,\n onAddField,\n onDataViewCreated,\n trigger,\n selectableProps,\n textBasedLanguages,\n onSaveTextLanguageQuery,\n onTextLangQuerySubmit,\n textBasedLanguage,\n onCreateDefaultAdHocDataView,\n isDisabled,\n}", + "label": "{\n isMissingCurrent,\n currentDataViewId,\n adHocDataViews,\n onChangeDataView,\n onEditDataView,\n onAddField,\n onDataViewCreated,\n trigger,\n selectableProps,\n textBasedLanguages,\n onSaveTextLanguageQuery,\n onTextLangQuerySubmit,\n textBasedLanguage,\n onCreateDefaultAdHocDataView,\n isDisabled,\n}", "description": [], "signature": [ "DataViewPickerPropsExtended" @@ -553,6 +553,54 @@ ], "returnComment": [] }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.DataViewPickerProps.onEditDataView", + "type": "Function", + "tags": [], + "label": "onEditDataView", + "description": [ + "\nCallback that is called when the user edits the current data view via flyout.\nThe first parameter is the updated data view stub without fetched fields" + ], + "signature": [ + "((updatedDataViewStub: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ") => void) | undefined" + ], + "path": "src/plugins/unified_search/public/dataview_picker/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedSearch", + "id": "def-public.DataViewPickerProps.onEditDataView.$1", + "type": "Object", + "tags": [], + "label": "updatedDataViewStub", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + } + ], + "path": "src/plugins/unified_search/public/dataview_picker/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "unifiedSearch", "id": "def-public.DataViewPickerProps.currentDataViewId", @@ -1110,6 +1158,26 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "unifiedSearch", + "id": "def-public.IUnifiedSearchPluginServices.dataViewEditor", + "type": "Object", + "tags": [], + "label": "dataViewEditor", + "description": [], + "signature": [ + { + "pluginId": "dataViewEditor", + "scope": "public", + "docId": "kibDataViewEditorPluginApi", + "section": "def-public.PluginStart", + "text": "PluginStart" + } + ], + "path": "src/plugins/unified_search/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedSearch", "id": "def-public.IUnifiedSearchPluginServices.usageCollection", diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 7ea6ab07192c..bdeec48aa8ea 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-10-04 +date: 2022-10-10 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 | |-------------------|-----------|------------------------|-----------------| -| 128 | 2 | 102 | 17 | +| 131 | 2 | 104 | 17 | ## Client diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 76e6013841c7..191e2461415c 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-10-04 +date: 2022-10-10 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 | |-------------------|-----------|------------------------|-----------------| -| 128 | 2 | 102 | 17 | +| 131 | 2 | 104 | 17 | ## Client diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index bc3da076b591..47c97e65e672 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index b67acbd363bf..f8c3a14d66b2 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 1c83be4e3438..f4689a3d2597 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-10-04 +date: 2022-10-10 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 7c1cb5bae380..b292069fca38 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 4b95ba2b035f..f36a18ef40bd 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index 1ea7a1e4b925..89dfc16e9f52 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 3ffea5a4f58e..1e7017d80aab 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 414b4de0b236..79dde1fcfab4 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 9306f4a52fd3..21d856b2f4dd 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 8d5ffda6491b..2c7ec032c466 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 9a7a8eb69828..fba1745b81e6 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 443abb630d8d..e211f35e5c3c 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-10-04 +date: 2022-10-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 958b60a5150f..db8a77c92427 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-10-04 +date: 2022-10-10 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 befca8a83486..e283e14d8ba8 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -9481,6 +9481,42 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.MinMax", + "type": "Interface", + "tags": [], + "label": "MinMax", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.MinMax.min", + "type": "number", + "tags": [], + "label": "min", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MinMax.max", + "type": "number", + "tags": [], + "label": "max", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.MovingAverageParams", @@ -13164,6 +13200,29 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.PercentageModeConfig", + "type": "Type", + "tags": [], + "label": "PercentageModeConfig", + "description": [], + "signature": [ + "({ isPercentageMode: true; } & ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.MinMax", + "text": "MinMax" + }, + ") | { isPercentageMode: boolean; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.PercentileColumn", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 4c4afd430bce..e09c5b43fe52 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-10-04 +date: 2022-10-10 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 | |-------------------|-----------|------------------------|-----------------| -| 725 | 12 | 695 | 18 | +| 729 | 12 | 699 | 18 | ## Client diff --git a/docs/api-generated/README.md b/docs/api-generated/README.md new file mode 100644 index 000000000000..4ce033e47ea0 --- /dev/null +++ b/docs/api-generated/README.md @@ -0,0 +1,36 @@ +# OpenAPI (Experimental) + +Open API specifications (OAS) exist in JSON or YAML format for some Kibana features, +though they are experimental and may be incomplete or change later. + +A preview of the API specifications can be added to the Kibana Guide by using +the following process: + +. Install [OpenAPI Generator](https://openapi-generator.tech/docs/installation), +or a similar tool that can generate HTML output from OAS. + +. Optionally validate the specifications by using the commands listed in the appropriate readmes. + +. Generate HTML output. For example: + + ``` + openapi-generator-cli generate -g html -i ~/kibana/x-pack/plugins/cases/docs/openapi/entrypoint.yaml -o ~/kibana/docs/api-generated/cases -t ~/kibana/docs/api-generated/template + + openapi-generator-cli generate -g html -i ~/kibana/x-pack/plugins/ml/common/openapi/ml_apis_v3.yaml -o ~/kibana/docs/api-generated/machine-learning -t ~/kibana/docs/api-generated/template + ``` + +. Rename the output files. For example: + ``` + mv ~/kibana/docs/api-generated/cases/index.html case-apis-passthru.asciidoc + mv ~/kibana/docs/api-generated/machine-learning/index.html ml-apis-passthru.adoc + ``` + +. If you're creating a new set of API output, you will need to have a page that incorporates the output by using passthrough blocks. For more information, refer to [Asciidoctor docs](https://docs.asciidoctor.org/asciidoc/latest/pass/pass-block/) + +. Verify the output by building the Kibana documentation. At this time, the output is added as a technical preview in the appendix. + +## Known issues + +- Some OAS 3.0 features such as `anyOf`, `oneOf`, and `allOf` might not display properly in the preview. These are on the [Short-term roadmap](https://openapi-generator.tech/docs/roadmap/) at this time. + + diff --git a/docs/api-generated/cases/case-apis-passthru.asciidoc b/docs/api-generated/cases/case-apis-passthru.asciidoc new file mode 100644 index 000000000000..2a07283aa98e --- /dev/null +++ b/docs/api-generated/cases/case-apis-passthru.asciidoc @@ -0,0 +1,827 @@ +//// +This content is generated from the open API specification. +Any modifications made to this file will be overwritten. +//// + +++++ +
+

Access

+
    +
  1. APIKey KeyParamName:ApiKey KeyInQuery:false KeyInHeader:true
  2. +
  3. HTTP Basic Authentication
  4. +
+ +

Methods

+ [ Jump to Models ] + +

Table of Contents

+
+

Cases

+ + +

Cases

+
+
+ Up +
post /s/{spaceId}/api/cases/{caseId}/comments
+
Adds a comment or alert to a case. (addCaseComment)
+
You must have all privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're creating.
+ +

Path parameters

+
+
caseId (required)
+ +
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
spaceId (required)
+ +
Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
+
+ +

Consumes

+ This API call consumes the following media types via the Content-Type request header: +
    +
  • application/json
  • +
+ +

Request body

+
+
add_case_comment_request add_case_comment_request (required)
+ +
Body Parameter
+ +
+ +

Request headers

+
+
kbn-xsrf (required)
+ +
Header Parameter — default: null
+ +
+ + + +

Return type

+ + + + +

Example data

+
Content-Type: application/json
+
{
+  "owner" : "cases",
+  "totalComment" : 0,
+  "settings" : {
+    "syncAlerts" : true
+  },
+  "totalAlerts" : 0,
+  "closed_at" : "2000-01-23T04:56:07.000+00:00",
+  "comments" : [ null, null ],
+  "created_at" : "2022-05-13T09:16:17.416Z",
+  "description" : "A case description.",
+  "title" : "Case title 1",
+  "created_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "version" : "WzUzMiwxXQ==",
+  "closed_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "tags" : [ "tag-1" ],
+  "duration" : 120,
+  "connector" : {
+    "name" : "none",
+    "id" : "none",
+    "fields" : {
+      "destIp" : "destIp",
+      "severity" : "severity",
+      "parent" : "parent",
+      "impact" : "impact",
+      "malwareUrl" : "malwareUrl",
+      "priority" : "priority",
+      "issueTypes" : [ 0.8008281904610115, 0.8008281904610115 ],
+      "issueType" : "issueType",
+      "sourceIp" : "sourceIp",
+      "urgency" : "urgency",
+      "malwareHash" : "malwareHash",
+      "caseId" : "caseId",
+      "severityCode" : 6.027456183070403,
+      "category" : "category",
+      "subcategory" : "subcategory"
+    },
+    "type" : ".none"
+  },
+  "updated_at" : "2000-01-23T04:56:07.000+00:00",
+  "updated_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+  "external_service" : {
+    "external_title" : "external_title",
+    "pushed_by" : {
+      "full_name" : "full_name",
+      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+      "email" : "email",
+      "username" : "elastic"
+    },
+    "external_url" : "external_url",
+    "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+    "connector_id" : "connector_id",
+    "external_id" : "external_id",
+    "connector_name" : "connector_name"
+  }
+}
+ +

Produces

+ This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
    +
  • application/json
  • +
+ +

Responses

+

200

+ Indicates a successful call. + case_response_properties +
+
+
+
+ Up +
delete /s/{spaceId}/api/cases/{caseId}/comments
+
Deletes all comments and alerts from a case. (deleteCaseComments)
+
You must have all privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're deleting.
+ +

Path parameters

+
+
caseId (required)
+ +
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
spaceId (required)
+ +
Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
+
+ + + +

Request headers

+
+
kbn-xsrf (required)
+ +
Header Parameter — default: null
+ +
+ + + + + + + + +

Responses

+

204

+ Indicates a successful call. + +
+
+
+
+ Up +
get /s/{spaceId}/api/cases/{caseId}/comments
+
Retrieves all the comments from a case. (getAllCaseComments)
+
You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.
+ +

Path parameters

+
+
caseId (required)
+ +
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
spaceId (required)
+ +
Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
+
+ + + + + + +

Return type

+ + + + +

Example data

+
Content-Type: application/json
+
{
+  "owner" : "cases",
+  "totalComment" : 0,
+  "settings" : {
+    "syncAlerts" : true
+  },
+  "totalAlerts" : 0,
+  "closed_at" : "2000-01-23T04:56:07.000+00:00",
+  "comments" : [ null, null ],
+  "created_at" : "2022-05-13T09:16:17.416Z",
+  "description" : "A case description.",
+  "title" : "Case title 1",
+  "created_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "version" : "WzUzMiwxXQ==",
+  "closed_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "tags" : [ "tag-1" ],
+  "duration" : 120,
+  "connector" : {
+    "name" : "none",
+    "id" : "none",
+    "fields" : {
+      "destIp" : "destIp",
+      "severity" : "severity",
+      "parent" : "parent",
+      "impact" : "impact",
+      "malwareUrl" : "malwareUrl",
+      "priority" : "priority",
+      "issueTypes" : [ 0.8008281904610115, 0.8008281904610115 ],
+      "issueType" : "issueType",
+      "sourceIp" : "sourceIp",
+      "urgency" : "urgency",
+      "malwareHash" : "malwareHash",
+      "caseId" : "caseId",
+      "severityCode" : 6.027456183070403,
+      "category" : "category",
+      "subcategory" : "subcategory"
+    },
+    "type" : ".none"
+  },
+  "updated_at" : "2000-01-23T04:56:07.000+00:00",
+  "updated_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+  "external_service" : {
+    "external_title" : "external_title",
+    "pushed_by" : {
+      "full_name" : "full_name",
+      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+      "email" : "email",
+      "username" : "elastic"
+    },
+    "external_url" : "external_url",
+    "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+    "connector_id" : "connector_id",
+    "external_id" : "external_id",
+    "connector_name" : "connector_name"
+  }
+}
+ +

Produces

+ This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
    +
  • application/json
  • +
+ +

Responses

+

200

+ Indicates a successful call. + case_response_properties +
+
+
+
+ Up +
patch /s/{spaceId}/api/cases/{caseId}/comments
+
Updates a comment or alert in a case. (updateCaseComment)
+
You must have all privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.
+ +

Path parameters

+
+
caseId (required)
+ +
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
spaceId (required)
+ +
Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
+
+ +

Consumes

+ This API call consumes the following media types via the Content-Type request header: +
    +
  • application/json
  • +
+ +

Request body

+
+
update_case_comment_request update_case_comment_request (required)
+ +
Body Parameter
+ +
+ +

Request headers

+
+
kbn-xsrf (required)
+ +
Header Parameter — default: null
+ +
+ + + +

Return type

+ + + + +

Example data

+
Content-Type: application/json
+
{
+  "owner" : "cases",
+  "totalComment" : 0,
+  "settings" : {
+    "syncAlerts" : true
+  },
+  "totalAlerts" : 0,
+  "closed_at" : "2000-01-23T04:56:07.000+00:00",
+  "comments" : [ null, null ],
+  "created_at" : "2022-05-13T09:16:17.416Z",
+  "description" : "A case description.",
+  "title" : "Case title 1",
+  "created_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "version" : "WzUzMiwxXQ==",
+  "closed_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "tags" : [ "tag-1" ],
+  "duration" : 120,
+  "connector" : {
+    "name" : "none",
+    "id" : "none",
+    "fields" : {
+      "destIp" : "destIp",
+      "severity" : "severity",
+      "parent" : "parent",
+      "impact" : "impact",
+      "malwareUrl" : "malwareUrl",
+      "priority" : "priority",
+      "issueTypes" : [ 0.8008281904610115, 0.8008281904610115 ],
+      "issueType" : "issueType",
+      "sourceIp" : "sourceIp",
+      "urgency" : "urgency",
+      "malwareHash" : "malwareHash",
+      "caseId" : "caseId",
+      "severityCode" : 6.027456183070403,
+      "category" : "category",
+      "subcategory" : "subcategory"
+    },
+    "type" : ".none"
+  },
+  "updated_at" : "2000-01-23T04:56:07.000+00:00",
+  "updated_by" : {
+    "full_name" : "full_name",
+    "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+    "email" : "email",
+    "username" : "elastic"
+  },
+  "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+  "external_service" : {
+    "external_title" : "external_title",
+    "pushed_by" : {
+      "full_name" : "full_name",
+      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+      "email" : "email",
+      "username" : "elastic"
+    },
+    "external_url" : "external_url",
+    "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+    "connector_id" : "connector_id",
+    "external_id" : "external_id",
+    "connector_name" : "connector_name"
+  }
+}
+ +

Produces

+ This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
    +
  • application/json
  • +
+ +

Responses

+

200

+ Indicates a successful call. + case_response_properties +
+
+ +

Models

+ [ Jump to Methods ] + +

Table of Contents

+
    +
  1. Case_response_properties_for_comments_inner -
  2. +
  3. Case_response_properties_for_connectors - Case response properties for connectors
  4. +
  5. add_alert_comment_request_properties - Add case comment request properties for alerts
  6. +
  7. add_case_comment_request - Add case comment request
  8. +
  9. add_user_comment_request_properties - Add case comment request properties for user comments
  10. +
  11. alert_comment_response_properties - Add case comment response properties for alerts
  12. +
  13. alert_comment_response_properties_created_by -
  14. +
  15. alert_comment_response_properties_pushed_by -
  16. +
  17. alert_comment_response_properties_rule -
  18. +
  19. alert_identifiers - Alert identifiers
  20. +
  21. alert_indices - Alert indices
  22. +
  23. case_response_closed_by_properties - Case response properties for closed_by
  24. +
  25. case_response_connector_field_properties - Case response properties for connector fields
  26. +
  27. case_response_created_by_properties - Case response properties for created_by
  28. +
  29. case_response_properties - Case response properties
  30. +
  31. case_response_pushed_by_properties - Case response properties for pushed_by
  32. +
  33. case_response_updated_by_properties - Case response properties for updated_by
  34. +
  35. connector_types -
  36. +
  37. external_service -
  38. +
  39. owners -
  40. +
  41. rule - Alerting rule
  42. +
  43. settings -
  44. +
  45. severity_property -
  46. +
  47. status -
  48. +
  49. update_alert_comment_request_properties - Update case comment request properties for alerts
  50. +
  51. update_case_comment_request - Update case comment request
  52. +
  53. update_user_comment_request_properties - Update case comment request properties for user comments
  54. +
  55. user_comment_response_properties - Case response properties for user comments
  56. +
+ +
+

Case_response_properties_for_comments_inner - Up

+
+
+
alertId (optional)
+
created_at (optional)
Date format: date-time
+
created_by (optional)
+
id (optional)
+
index (optional)
+
owner (optional)
+
pushed_at (optional)
Date format: date-time
+
pushed_by (optional)
+
rule (optional)
+
type
+
Enum:
+
user
+
updated_at (optional)
Date format: date-time
+
updated_by (optional)
+
version (optional)
+
comment (optional)
+
+
+
+

Case_response_properties_for_connectors - Case response properties for connectors Up

+
+
+
fields (optional)
+
id (optional)
String The identifier for the connector. To create a case without a connector, use none.
+
name (optional)
String The name of the connector. To create a case without a connector, use none.
+
type (optional)
+
+
+
+

add_alert_comment_request_properties - Add case comment request properties for alerts Up

+
Defines properties for case comment requests when type is alert.
+
+
alertId
+
index
+
owner
+
rule
+
type
String The type of comment.
+
Enum:
+
alert
+
+
+
+

add_case_comment_request - Add case comment request Up

+
The add comment to case API request body varies depending on whether you are adding an alert or a comment.
+
+
alertId
+
index
+
owner
+
rule
+
type
String The type of comment.
+
Enum:
+
user
+
comment
String The new comment. It is required only when type is user.
+
+
+
+

add_user_comment_request_properties - Add case comment request properties for user comments Up

+
Defines properties for case comment requests when type is user.
+
+
comment
String The new comment. It is required only when type is user.
+
owner
+
type
String The type of comment.
+
Enum:
+
user
+
+
+
+

alert_comment_response_properties - Add case comment response properties for alerts Up

+
+
+
alertId (optional)
+
created_at (optional)
Date format: date-time
+
created_by (optional)
+
id (optional)
+
index (optional)
+
owner (optional)
+
pushed_at (optional)
Date format: date-time
+
pushed_by (optional)
+
rule (optional)
+
type
+
Enum:
+
alert
+
updated_at (optional)
Date format: date-time
+
updated_by (optional)
+
version (optional)
+
+
+
+

alert_comment_response_properties_created_by - Up

+
+
+
email (optional)
+
full_name (optional)
+
username (optional)
+
profile_uid (optional)
+
+
+
+

alert_comment_response_properties_pushed_by - Up

+
+
+
email (optional)
+
full_name (optional)
+
username (optional)
+
profile_uid (optional)
+
+
+
+

alert_comment_response_properties_rule - Up

+
+
+
id (optional)
String The rule identifier.
+
name (optional)
String The rule name.
+
+
+
+

alert_identifiers - Alert identifiers Up

+
The alert identifier. It is required only when type is alert. If it is an array, index must also be an array with the same length or number of elements. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
+
+
+
+
+

alert_indices - Alert indices Up

+
The alert index. It is required only when type is alert. If it is an array, alertId must also be an array with the same length or number of elements. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
+
+
+
+
+

case_response_closed_by_properties - Case response properties for closed_by Up

+
+
+
email
+
full_name
+
username
+
profile_uid (optional)
+
+
+
+

case_response_connector_field_properties - Case response properties for connector fields Up

+
An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.
+
+
caseId (optional)
String The case identifier for Swimlane connectors.
+
category (optional)
String The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.
+
destIp (optional)
String A comma-separated list of destination IPs for ServiceNow SecOps connectors.
+
impact (optional)
String The effect an incident had on business for ServiceNow ITSM connectors.
+
issueType (optional)
String The type of issue for Jira connectors.
+
issueTypes (optional)
array[BigDecimal] The type of incident for IBM Resilient connectors.
+
malwareHash (optional)
String A comma-separated list of malware hashes for ServiceNow SecOps connectors.
+
malwareUrl (optional)
String A comma-separated list of malware URLs for ServiceNow SecOps connectors.
+
parent (optional)
String The key of the parent issue, when the issue type is sub-task for Jira connectors.
+
priority (optional)
String The priority of the issue for Jira and ServiceNow SecOps connectors.
+
severity (optional)
String The severity of the incident for ServiceNow ITSM connectors.
+
severityCode (optional)
BigDecimal The severity code of the incident for IBM Resilient connectors.
+
sourceIp (optional)
String A comma-separated list of source IPs for ServiceNow SecOps connectors.
+
subcategory (optional)
String The subcategory of the incident for ServiceNow ITSM connectors.
+
urgency (optional)
String The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.
+
+
+
+

case_response_created_by_properties - Case response properties for created_by Up

+
+
+
email
+
full_name
+
username
+
profile_uid (optional)
+
+
+
+

case_response_properties - Case response properties Up

+
+
+
closed_at
Date format: date-time
+
closed_by
+
comments
array[Case_response_properties_for_comments_inner] An array of comment objects for the case.
+
connector
+
created_at
Date format: date-time
+
created_by
+
description
+
duration
Integer The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.
+
external_service
+
id
+
owner
+
settings
+
severity
+
status
+
tags
+
title
+
totalAlerts
+
totalComment
+
updated_at
Date format: date-time
+
updated_by
+
version
+
+
+
+

case_response_pushed_by_properties - Case response properties for pushed_by Up

+
+
+
email
+
full_name
+
username
+
profile_uid (optional)
+
+
+
+

case_response_updated_by_properties - Case response properties for updated_by Up

+
+
+
email
+
full_name
+
username
+
profile_uid (optional)
+
+
+
+

connector_types - Up

+
The type of connector.
+
+
+
+
+

external_service - Up

+
+
+
connector_id (optional)
+
connector_name (optional)
+
external_id (optional)
+
external_title (optional)
+
external_url (optional)
+
pushed_at (optional)
Date format: date-time
+
pushed_by (optional)
+
+
+
+

owners - Up

+
The application that owns the cases: Stack Management, Observability, or Elastic Security.
+
+
+
+
+

rule - Alerting rule Up

+
The rule that is associated with the alert. It is required only when type is alert. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
+
+
id (optional)
String The rule identifier.
+
name (optional)
String The rule name.
+
+
+
+

settings - Up

+
An object that contains the case settings.
+
+
syncAlerts (optional)
Boolean Turns alert syncing on or off.
+
+
+
+

severity_property - Up

+
The severity of the case.
+
+
+
+
+

status - Up

+
The status of the case.
+
+
+
+
+

update_alert_comment_request_properties - Update case comment request properties for alerts Up

+
Defines properties for case comment requests when type is alert.
+
+
alertId
+
id
String The identifier for the comment. To retrieve comment IDs, use the get comments API.
+
index
+
owner
+
rule
+
type
String The type of comment.
+
Enum:
+
alert
+
version
String The current comment version. To retrieve version values, use the get comments API.
+
+
+
+

update_case_comment_request - Update case comment request Up

+
The update case comment API request body varies depending on whether you are updating an alert or a comment.
+
+
alertId
+
id
String The identifier for the comment. To retrieve comment IDs, use the get comments API.
+
index
+
owner
+
rule
+
type
String The type of comment.
+
Enum:
+
user
+
version
String The current comment version. To retrieve version values, use the get comments API.
+
comment
String The new comment. It is required only when type is user.
+
+
+
+

update_user_comment_request_properties - Update case comment request properties for user comments Up

+
Defines properties for case comment requests when type is user.
+
+
comment
String The new comment. It is required only when type is user.
+
id
String The identifier for the comment. To retrieve comment IDs, use the get comments API.
+
owner
+
type
String The type of comment.
+
Enum:
+
user
+
version
String The current comment version. To retrieve version values, use the get comments API.
+
+
+
+

user_comment_response_properties - Case response properties for user comments Up

+
+
+
comment (optional)
+
created_at (optional)
Date format: date-time
+
created_by (optional)
+
id (optional)
+
owner (optional)
+
pushed_at (optional)
Date format: date-time
+
pushed_by (optional)
+
type
+
Enum:
+
user
+
updated_at (optional)
Date format: date-time
+
updated_by (optional)
+
version (optional)
+
+
+
+++++ diff --git a/docs/api-generated/cases/case-apis.asciidoc b/docs/api-generated/cases/case-apis.asciidoc new file mode 100644 index 000000000000..fdd9a941a58e --- /dev/null +++ b/docs/api-generated/cases/case-apis.asciidoc @@ -0,0 +1,10 @@ +[[case-apis]] +== Case APIs + +preview::[] + +//// +This file includes content that has been generated from https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/docs/openapi. Any modifications required must be done in that open API specification. +//// + +include::case-apis-passthru.asciidoc[] \ No newline at end of file diff --git a/docs/api/cases/cases-api-add-comment.asciidoc b/docs/api/cases/cases-api-add-comment.asciidoc index 618f4a5de884..918f579f1c0d 100644 --- a/docs/api/cases/cases-api-add-comment.asciidoc +++ b/docs/api/cases/cases-api-add-comment.asciidoc @@ -6,6 +6,12 @@ Adds a comment or alert to a case. +[NOTE] +==== +For the most up-to-date API details, refer to the +{kib-repo}/tree/{branch}/x-pack/plugins/cases/docs/openapi[open API specification]. For a preview, check out <>. +==== + === {api-request-title} `POST :/api/cases//comments` diff --git a/docs/api/cases/cases-api-delete-comments.asciidoc b/docs/api/cases/cases-api-delete-comments.asciidoc index 50866cfbe85f..130158bd021c 100644 --- a/docs/api/cases/cases-api-delete-comments.asciidoc +++ b/docs/api/cases/cases-api-delete-comments.asciidoc @@ -6,6 +6,12 @@ Deletes one or all comments and alerts from a case. +[NOTE] +==== +For the most up-to-date API details, refer to the +{kib-repo}/tree/{branch}/x-pack/plugins/cases/docs/openapi[open API specification]. For a preview, check out <>. +==== + === {api-request-title} `DELETE :/api/cases//comments` diff --git a/docs/api/cases/cases-api-get-comments.asciidoc b/docs/api/cases/cases-api-get-comments.asciidoc index 58c4c32acfa1..5f7bb938f588 100644 --- a/docs/api/cases/cases-api-get-comments.asciidoc +++ b/docs/api/cases/cases-api-get-comments.asciidoc @@ -6,6 +6,12 @@ Gets a comment or all comments for a case. +[NOTE] +==== +For the most up-to-date API details, refer to the +{kib-repo}/tree/{branch}/x-pack/plugins/cases/docs/openapi[open API specification]. For a preview, check out <>. +==== + === {api-request-title} `GET :/api/cases//comments/` diff --git a/docs/api/cases/cases-api-update-comment.asciidoc b/docs/api/cases/cases-api-update-comment.asciidoc index 127d434602f8..4f2e89a7997e 100644 --- a/docs/api/cases/cases-api-update-comment.asciidoc +++ b/docs/api/cases/cases-api-update-comment.asciidoc @@ -6,6 +6,12 @@ Updates a comment or alert in a case. +[NOTE] +==== +For the most up-to-date API details, refer to the +{kib-repo}/tree/{branch}/x-pack/plugins/cases/docs/openapi[open API specification]. For a preview, check out <>. +==== + === {api-request-title} `PATCH :/api/cases//comments` diff --git a/docs/apis.asciidoc b/docs/apis.asciidoc index 8fb4caa7d3fc..8b07e58d4f8a 100644 --- a/docs/apis.asciidoc +++ b/docs/apis.asciidoc @@ -11,4 +11,5 @@ version of the specification is 3.0. For more information, go to https://openapi -- +include::api-generated/cases/case-apis.asciidoc[] include::api-generated/machine-learning/ml-apis.asciidoc[] \ No newline at end of file diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index ce8c7fb2011b..1e0788cb5676 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -762,7 +762,7 @@ POST /_security/role/apm_agent_key_user "cluster": ["manage_own_api_key"], "applications": [{ "application": "apm", - "privileges": ["event:write", "sourcemap:write", "config_agent:read"], + "privileges": ["event:write", "config_agent:read"], "resources": ["*"] }] } @@ -785,7 +785,6 @@ POST /_security/role/apm_agent_key_user - `event:write`. Required for ingesting agent events. - `config_agent:read`. Required for agents to read agent configuration remotely. - - `sourcemap:write`. Required for uploading sourcemaps. [[apm-agent-key-create-example]] ===== Example @@ -795,7 +794,7 @@ POST /_security/role/apm_agent_key_user POST /api/apm/agent_keys { "name": "apm-key", - "privileges": ["event:write", "config_agent:read", "sourcemap:write"] + "privileges": ["event:write", "config_agent:read"] } -------------------------------------------------- diff --git a/docs/concepts/data-views.asciidoc b/docs/concepts/data-views.asciidoc index 8726b3a55cbd..2ce89474a003 100644 --- a/docs/concepts/data-views.asciidoc +++ b/docs/concepts/data-views.asciidoc @@ -65,14 +65,14 @@ based on different timestamps. . To specify your own {data-source} name, click *Show advanced settings*, then enter the name in the *Custom {data-source} ID* field. For example, enter your {es} index alias name. -. Click *Save {data-source} to {kib}*. +. [[reload-fields]] To use this data in searches and visualizations that you intend to save, click *Save {data-source} to {kib}*. + +. To explore your data without creating a {data-source}, click *Use without saving*. + -[[reload-fields]] {kib} is now configured to use your {es} data. When a new field is added to an index, -the {data-source} field list is updated -the next time the {data-source} is loaded, for example, when you load the page or -move between {kib} apps. +This allows you to explore your data in *Discover*, *Lens*, and *Maps* +without making it visible to others in your space. You can save the {data-source} later +if you create a search or visualization that you want to share. -. Select this {data-source} when you search and visualize your data. [float] [[rollup-data-view]] diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 407261c6f1d7..4853d859b371 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -71,7 +71,7 @@ as uiSettings within the code. |{kib-repo}blob/{branch}/src/plugins/data_views/README.mdx[dataViews] |The data views API provides a consistent method of structuring and formatting documents -and field lists across the various Kibana apps. Its typically used in conjunction with +and field lists across the various Kibana apps. It's typically used in conjunction with for composing queries. diff --git a/docs/management/index-patterns/images/create-data-view.png b/docs/management/index-patterns/images/create-data-view.png index 229ed0f490b4..af5b01111cd2 100644 Binary files a/docs/management/index-patterns/images/create-data-view.png and b/docs/management/index-patterns/images/create-data-view.png differ diff --git a/docs/settings/search-sessions-settings.asciidoc b/docs/settings/search-sessions-settings.asciidoc index 7628ecfb397e..7a51c23c9a1c 100644 --- a/docs/settings/search-sessions-settings.asciidoc +++ b/docs/settings/search-sessions-settings.asciidoc @@ -10,9 +10,6 @@ Configure the search session settings in your `kibana.yml` configuration file. `data.search.sessions.enabled` {ess-icon}:: Set to `true` (default) to enable search sessions. -`data.search.sessions.trackingInterval` {ess-icon}:: -The frequency for updating the state of a search session. The default is `10s`. - `data.search.sessions.pageSize` {ess-icon}:: How many search sessions {kib} processes at once while monitoring session progress. The default is `100`. @@ -21,9 +18,6 @@ session progress. The default is `100`. How long {kib} stores search results from unsaved sessions, after the last search in the session completes. The default is `5m`. -`data.search.sessions.notTouchedInProgressTimeout` {ess-icon}:: -How long a search session can run after a user navigates away without saving a session. The default is `1m`. - `data.search.sessions.maxUpdateRetries` {ess-icon}:: How many retries {kib} can perform while attempting to save a search session. The default is `3`. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 47bc5642604b..69316af1593a 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -80,9 +80,18 @@ configuration is effectively ignored when <> is enable *Default: `true`* [[elasticsearch-maxSockets]] `elasticsearch.maxSockets`:: -The maximum number of sockets that can be used for communications with elasticsearch. +The maximum number of sockets that can be used for communications with {es}. *Default: `Infinity`* + +[[elasticsearch-maxIdleSockets]] `elasticsearch.maxIdleSockets`:: +The maximum number of idle sockets to keep open between {kib} and {es}. If more sockets become idle, they will be closed. +*Default: `256`* + +[[elasticsearch-idleSocketTimeout]] `elasticsearch.idleSocketTimeout`:: +The timeout for idle sockets kept open between {kib} and {es}. If the socket is idle for longer than this timeout, it will be closed. If you have a transparent proxy between {kib} and {es} be sure to set this value lower than or equal to the proxy's timeout. +*Default: `60s`* + `elasticsearch.customHeaders`:: | Header names and values to send to {es}. Any custom headers cannot be overwritten by client-side headers, regardless of the diff --git a/docs/user/alerting/rule-types/es-query.asciidoc b/docs/user/alerting/rule-types/es-query.asciidoc index 715fabc6fdc3..c838200e637e 100644 --- a/docs/user/alerting/rule-types/es-query.asciidoc +++ b/docs/user/alerting/rule-types/es-query.asciidoc @@ -26,7 +26,7 @@ the *time window*. Size:: Specifies the number of documents to pass to the configured actions when the threshold condition is met. {es} query:: Specifies the ES DSL query. The number of documents that -match this query is evaluated against the threshold condition. Only the `query`, `fields` and `runtime_mappings` +match this query is evaluated against the threshold condition. Only the `query`, `fields`, `_source` and `runtime_mappings` fields are used, other DSL fields are not considered. Threshold:: Defines a threshold value and a comparison operator (`is above`, `is above or equals`, `is below`, `is below or equals`, or `is between`). The diff --git a/fleet_packages.json b/fleet_packages.json index 4651e8628758..94d383ff9f6e 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -37,6 +37,6 @@ }, { "name": "synthetics", - "version": "0.10.2" + "version": "0.10.3" } -] \ No newline at end of file +] diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index b9fd0eef45c1..14e27632c33f 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -171,6 +171,9 @@ { "label": "Contributors Newsletters", "items": [ + { + "id": "kibSeptember2022ContributorNewsletter" + }, { "id": "kibAugust2022ContributorNewsletter" }, diff --git a/package.json b/package.json index 9825802a8029..bd550a1ea2c4 100644 --- a/package.json +++ b/package.json @@ -116,10 +116,11 @@ "@elastic/react-search-ui": "^1.14.0", "@elastic/request-crypto": "2.0.1", "@elastic/search-ui-app-search-connector": "^1.14.0", - "@emotion/cache": "^11.9.3", - "@emotion/css": "^11.9.0", - "@emotion/react": "^11.9.3", - "@emotion/serialize": "^1.0.4", + "@emotion/cache": "^11.10.3", + "@emotion/css": "^11.10.0", + "@emotion/react": "^11.10.4", + "@emotion/serialize": "^1.1.0", + "@emotion/server": "^11.10.0", "@grpc/grpc-js": "^1.6.7", "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.4", @@ -294,6 +295,7 @@ "@kbn/core-status-server-mocks": "link:bazel-bin/packages/core/status/core-status-server-mocks", "@kbn/core-test-helpers-deprecations-getters": "link:bazel-bin/packages/core/test-helpers/core-test-helpers-deprecations-getters", "@kbn/core-test-helpers-http-setup-browser": "link:bazel-bin/packages/core/test-helpers/core-test-helpers-http-setup-browser", + "@kbn/core-test-helpers-so-type-serializer": "link:bazel-bin/packages/core/test-helpers/core-test-helpers-so-type-serializer", "@kbn/core-theme-browser": "link:bazel-bin/packages/core/theme/core-theme-browser", "@kbn/core-theme-browser-internal": "link:bazel-bin/packages/core/theme/core-theme-browser-internal", "@kbn/core-theme-browser-mocks": "link:bazel-bin/packages/core/theme/core-theme-browser-mocks", @@ -443,6 +445,7 @@ "axios": "^0.27.2", "base64-js": "^1.3.1", "bitmap-sdf": "^1.0.3", + "blurhash": "^2.0.1", "brace": "0.11.1", "byte-size": "^8.1.0", "canvg": "^3.0.9", @@ -485,7 +488,6 @@ "fast-deep-equal": "^3.1.1", "fflate": "^0.6.9", "file-saver": "^1.3.8", - "fnv-plus": "^1.3.1", "font-awesome": "4.7.0", "formik": "^2.2.9", "fp-ts": "^2.3.1", @@ -661,13 +663,13 @@ }, "devDependencies": { "@apidevtools/swagger-parser": "^10.0.3", - "@babel/cli": "^7.18.10", - "@babel/core": "^7.19.1", + "@babel/cli": "^7.19.3", + "@babel/core": "^7.19.3", "@babel/eslint-parser": "^7.19.1", "@babel/eslint-plugin": "^7.19.1", - "@babel/generator": "^7.19.0", + "@babel/generator": "^7.19.3", "@babel/helper-plugin-utils": "^7.19.0", - "@babel/parser": "^7.19.1", + "@babel/parser": "^7.19.3", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", @@ -675,12 +677,12 @@ "@babel/plugin-proposal-optional-chaining": "^7.18.9", "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-transform-runtime": "^7.19.1", - "@babel/preset-env": "^7.19.1", + "@babel/preset-env": "^7.19.3", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@babel/register": "^7.18.9", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0", + "@babel/traverse": "^7.19.3", + "@babel/types": "^7.19.3", "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", "@cypress/code-coverage": "^3.10.0", @@ -690,8 +692,8 @@ "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^6.0.0", "@elastic/synthetics": "^1.0.0-beta.22", - "@emotion/babel-preset-css-prop": "^11.2.0", - "@emotion/jest": "^11.9.4", + "@emotion/babel-preset-css-prop": "^11.10.0", + "@emotion/jest": "^11.10.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/schema": "^0.1.2", "@jest/console": "^26.6.2", @@ -819,7 +821,6 @@ "@types/fetch-mock": "^7.3.1", "@types/file-saver": "^2.0.0", "@types/flot": "^0.0.31", - "@types/fnv-plus": "^1.3.0", "@types/geojson": "7946.0.7", "@types/getos": "^3.0.0", "@types/gulp": "^4.0.6", @@ -1026,6 +1027,7 @@ "@types/kbn__core-status-server-mocks": "link:bazel-bin/packages/core/status/core-status-server-mocks/npm_module_types", "@types/kbn__core-test-helpers-deprecations-getters": "link:bazel-bin/packages/core/test-helpers/core-test-helpers-deprecations-getters/npm_module_types", "@types/kbn__core-test-helpers-http-setup-browser": "link:bazel-bin/packages/core/test-helpers/core-test-helpers-http-setup-browser/npm_module_types", + "@types/kbn__core-test-helpers-so-type-serializer": "link:bazel-bin/packages/core/test-helpers/core-test-helpers-so-type-serializer/npm_module_types", "@types/kbn__core-theme-browser": "link:bazel-bin/packages/core/theme/core-theme-browser/npm_module_types", "@types/kbn__core-theme-browser-internal": "link:bazel-bin/packages/core/theme/core-theme-browser-internal/npm_module_types", "@types/kbn__core-theme-browser-mocks": "link:bazel-bin/packages/core/theme/core-theme-browser-mocks/npm_module_types", @@ -1285,7 +1287,7 @@ "babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-styled-components": "^2.0.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "backport": "^8.9.4", + "backport": "^8.9.7", "callsites": "^3.1.0", "chance": "1.0.18", "chokidar": "^3.5.3", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 97b7064f4cd7..aae46a111c05 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -160,6 +160,7 @@ filegroup( "//packages/core/status/core-status-server-mocks:build", "//packages/core/test-helpers/core-test-helpers-deprecations-getters:build", "//packages/core/test-helpers/core-test-helpers-http-setup-browser:build", + "//packages/core/test-helpers/core-test-helpers-so-type-serializer:build", "//packages/core/theme/core-theme-browser:build", "//packages/core/theme/core-theme-browser-internal:build", "//packages/core/theme/core-theme-browser-mocks:build", @@ -500,6 +501,7 @@ filegroup( "//packages/core/status/core-status-server-mocks:build_types", "//packages/core/test-helpers/core-test-helpers-deprecations-getters:build_types", "//packages/core/test-helpers/core-test-helpers-http-setup-browser:build_types", + "//packages/core/test-helpers/core-test-helpers-so-type-serializer:build_types", "//packages/core/theme/core-theme-browser:build_types", "//packages/core/theme/core-theme-browser-internal:build_types", "//packages/core/theme/core-theme-browser-mocks:build_types", 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 dfa8a077d2e5..0f374bd8311e 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 @@ -44,35 +44,27 @@ describe('AgentManager', () => { expect(httpsAgent).toEqual(mockedHttpsAgent); }); - it('provides Agents with a valid default configuration', () => { - const agentManager = new AgentManager(); - const agentFactory = agentManager.getAgentFactory(); - agentFactory({ url: new URL('http://elastic-node-1:9200') }); - expect(HttpAgent).toBeCalledTimes(1); - expect(HttpAgent).toBeCalledWith({ - keepAlive: true, - keepAliveMsecs: 1000, - maxFreeSockets: 256, - maxSockets: 256, - scheduling: 'lifo', - }); - }); - it('takes into account the provided configurations', () => { - const agentManager = new AgentManager({ maxFreeSockets: 32, maxSockets: 2048 }); + const agentManager = new AgentManager(); const agentFactory = agentManager.getAgentFactory({ - maxSockets: 1024, + maxTotalSockets: 1024, scheduling: 'fifo', }); agentFactory({ url: new URL('http://elastic-node-1:9200') }); - expect(HttpAgent).toBeCalledTimes(1); + const agentFactory2 = agentManager.getAgentFactory({ + maxFreeSockets: 10, + scheduling: 'lifo', + }); + agentFactory2({ url: new URL('http://elastic-node-2:9200') }); + expect(HttpAgent).toBeCalledTimes(2); expect(HttpAgent).toBeCalledWith({ - keepAlive: true, - keepAliveMsecs: 1000, - maxFreeSockets: 32, - maxSockets: 1024, + maxTotalSockets: 1024, scheduling: 'fifo', }); + expect(HttpAgent).toBeCalledWith({ + maxFreeSockets: 10, + scheduling: 'lifo', + }); }); it('provides Agents that match the URLs protocol', () => { @@ -86,7 +78,7 @@ describe('AgentManager', () => { expect(HttpsAgent).toHaveBeenCalledTimes(1); }); - it('provides the same Agent iif URLs use the same protocol', () => { + it('provides the same Agent if URLs use the same protocol', () => { const agentManager = new AgentManager(); const agentFactory = agentManager.getAgentFactory(); const agent1 = agentFactory({ url: new URL('http://elastic-node-1:9200') }); 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 9a57cc44e04a..9f654216bdce 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 @@ -6,18 +6,11 @@ * Side Public License, v 1. */ -import { Agent as HttpAgent } from 'http'; +import { Agent as HttpAgent, type AgentOptions } from 'http'; import { Agent as HttpsAgent } from 'https'; import type { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch'; const HTTPS = 'https:'; -const DEFAULT_CONFIG: HttpAgentOptions = { - keepAlive: true, - keepAliveMsecs: 1000, - maxSockets: 256, - maxFreeSockets: 256, - scheduling: 'lifo', -}; export type NetworkAgent = HttpAgent | HttpsAgent; export type AgentFactory = (connectionOpts: ConnectionOptions) => NetworkAgent; @@ -44,11 +37,11 @@ export interface AgentStore { export class AgentManager implements AgentFactoryProvider, AgentStore { private agents: Set; - constructor(private agentOptions: HttpAgentOptions = DEFAULT_CONFIG) { + constructor() { this.agents = new Set(); } - public getAgentFactory(agentOptions?: HttpAgentOptions): AgentFactory { + public getAgentFactory(agentOptions?: AgentOptions): AgentFactory { // a given agent factory always provides the same Agent instances (for the same protocol) // we keep references to the instances at factory level, to be able to reuse them let httpAgent: HttpAgent; @@ -57,13 +50,7 @@ export class AgentManager implements AgentFactoryProvider, AgentStore { return (connectionOpts: ConnectionOptions): NetworkAgent => { if (connectionOpts.url.protocol === HTTPS) { if (!httpsAgent) { - const config = Object.assign( - {}, - DEFAULT_CONFIG, - this.agentOptions, - agentOptions, - connectionOpts.tls - ); + const config = Object.assign({}, agentOptions, connectionOpts.tls); httpsAgent = new HttpsAgent(config); this.agents.add(httpsAgent); dereferenceOnDestroy(this.agents, httpsAgent); @@ -73,8 +60,7 @@ export class AgentManager implements AgentFactoryProvider, AgentStore { } if (!httpAgent) { - const config = Object.assign({}, DEFAULT_CONFIG, this.agentOptions, agentOptions); - httpAgent = new HttpAgent(config); + httpAgent = new HttpAgent(agentOptions); this.agents.add(httpAgent); dereferenceOnDestroy(this.agents, httpAgent); } diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.test.ts index 3c791c44bcb4..50424ddf3746 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.test.ts @@ -18,6 +18,8 @@ const createConfig = ( customHeaders: {}, compression: false, maxSockets: Infinity, + maxIdleSockets: 300, + idleSocketTimeout: duration(30, 'seconds'), sniffOnStart: false, sniffOnConnectionFault: false, sniffInterval: false, @@ -41,15 +43,13 @@ describe('parseClientOptions', () => { ); }); - it('specifies `headers.maxSockets` Infinity and `keepAlive` true by default', () => { + it('specifies `maxTotalSockets` Infinity and `keepAlive` true by default', () => { const config = createConfig({}); - expect(parseClientOptions(config, false, kibanaVersion)).toEqual( + expect(parseClientOptions(config, false, kibanaVersion).agent).toEqual( expect.objectContaining({ - agent: { - keepAlive: true, - maxSockets: Infinity, - }, + keepAlive: true, + maxTotalSockets: Infinity, }) ); }); @@ -119,18 +119,35 @@ describe('parseClientOptions', () => { }); describe('`maxSockets` option', () => { - it('uses the specified config value', () => { + it('sets the agent.maxTotalSockets config value', () => { const options = parseClientOptions( createConfig({ maxSockets: 1024 }), false, kibanaVersion ); - expect(options.agent).toHaveProperty('maxSockets', 1024); + expect(options.agent).toHaveProperty('maxTotalSockets', 1024); }); + }); - it('defaults to `Infinity` if not specified by the config', () => { - const options = parseClientOptions(createConfig({}), false, kibanaVersion); - expect(options.agent).toHaveProperty('maxSockets', Infinity); + describe('`maxIdleSockets` option', () => { + it('sets the agent.maxFreeSockets config value', () => { + const options = parseClientOptions( + createConfig({ maxIdleSockets: 1024 }), + false, + kibanaVersion + ); + expect(options.agent).toHaveProperty('maxFreeSockets', 1024); + }); + }); + + describe('`idleSocketTimeout` option', () => { + it('sets the agent.timeout config value', () => { + const options = parseClientOptions( + createConfig({ idleSocketTimeout: duration(1000, 's') }), + false, + kibanaVersion + ); + expect(options.agent).toHaveProperty('timeout', 1_000_000); }); }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.ts index b0142ee0b158..58660a991734 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.ts @@ -9,11 +9,12 @@ import { ConnectionOptions as TlsConnectionOptions } from 'tls'; import { URL } from 'url'; import { Duration } from 'moment'; -import type { ClientOptions, HttpAgentOptions } from '@elastic/elasticsearch'; +import type { ClientOptions } from '@elastic/elasticsearch'; import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; +import { AgentOptions } from 'https'; import { getDefaultHeaders } from './headers'; -export type ParsedClientOptions = Omit & { agent: HttpAgentOptions }; +export type ParsedClientOptions = Omit & { agent: AgentOptions }; /** * Parse the client options from given client config and `scoped` flag. @@ -38,8 +39,10 @@ export function parseClientOptions( // fixes https://github.com/elastic/kibana/issues/101944 disablePrototypePoisoningProtection: true, agent: { - maxSockets: config.maxSockets, + maxTotalSockets: config.maxSockets, keepAlive: config.keepAlive ?? true, + timeout: getDurationAsMs(config.idleSocketTimeout), + maxFreeSockets: config.maxIdleSockets, }, compression: config.compression, }; diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts index f371e3425b0c..bcf5011395bf 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts @@ -18,6 +18,7 @@ import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { ClusterClient } from './cluster_client'; import { DEFAULT_HEADERS, getDefaultHeaders } from './headers'; import { AgentManager } from './agent_manager'; +import { duration } from 'moment'; const createConfig = ( parts: Partial = {} @@ -27,6 +28,8 @@ const createConfig = ( sniffOnConnectionFault: false, sniffInterval: false, maxSockets: Infinity, + maxIdleSockets: 200, + idleSocketTimeout: duration('30s'), compression: false, requestHeadersWhitelist: ['authorization'], customHeaders: {}, diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts index f8299533fecc..b81683d1cc11 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts @@ -36,7 +36,9 @@ test('set correct defaults', () => { "hosts": Array [ "http://localhost:9200", ], + "idleSocketTimeout": "PT1M", "ignoreVersionMismatch": false, + "maxIdleSockets": 256, "maxSockets": Infinity, "password": undefined, "pingTimeout": "PT30S", diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts index 8420d8d72346..bc2ec78acac0 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts @@ -37,6 +37,8 @@ export const configSchema = schema.object({ defaultValue: 'http://localhost:9200', }), maxSockets: schema.number({ defaultValue: Infinity, min: 1 }), + maxIdleSockets: schema.number({ defaultValue: 256, min: 1 }), + idleSocketTimeout: schema.duration({ defaultValue: '60s' }), compression: schema.boolean({ defaultValue: false }), username: schema.maybe( schema.string({ @@ -304,6 +306,16 @@ export class ElasticsearchConfig implements IElasticsearchConfig { */ public readonly maxSockets: number; + /** + * The maximum number of idle sockets to keep open between Kibana and Elasticsearch. If more sockets become idle, they will be closed. + */ + public readonly maxIdleSockets: number; + + /** + * The timeout for idle sockets kept open between Kibana and Elasticsearch. If the socket is idle for longer than this timeout, it will be closed. + */ + public readonly idleSocketTimeout: Duration; + /** * Whether to use compression for communications with elasticsearch. */ @@ -409,6 +421,8 @@ export class ElasticsearchConfig implements IElasticsearchConfig { this.serviceAccountToken = rawConfig.serviceAccountToken; this.customHeaders = rawConfig.customHeaders; this.maxSockets = rawConfig.maxSockets; + this.maxIdleSockets = rawConfig.maxIdleSockets; + this.idleSocketTimeout = rawConfig.idleSocketTimeout; this.compression = rawConfig.compression; this.skipStartupConnectionCheck = rawConfig.skipStartupConnectionCheck; diff --git a/packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts b/packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts index 8c8fa6343e54..8bd5003e903a 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts @@ -17,6 +17,8 @@ export interface ElasticsearchClientConfig { customHeaders: Record; requestHeadersWhitelist: string[]; maxSockets: number; + maxIdleSockets: number; + idleSocketTimeout: Duration; compression: boolean; sniffOnStart: boolean; sniffOnConnectionFault: boolean; diff --git a/packages/core/elasticsearch/core-elasticsearch-server/src/elasticsearch_config.ts b/packages/core/elasticsearch/core-elasticsearch-server/src/elasticsearch_config.ts index a0cd6a34cca8..ae1dc281b29f 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/src/elasticsearch_config.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server/src/elasticsearch_config.ts @@ -32,6 +32,16 @@ export interface IElasticsearchConfig { */ readonly maxSockets: number; + /** + * The maximum number of idle sockets to keep open between Kibana and Elasticsearch. If more sockets become idle, they will be closed. + */ + readonly maxIdleSockets: number; + + /** + * The timeout for idle sockets kept open between Kibana and Elasticsearch. If the socket is idle for longer than this timeout, it will be closed. + */ + readonly idleSocketTimeout: Duration; + /** * Whether to use compression for communications with elasticsearch. */ diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/BUILD.bazel b/packages/core/test-helpers/core-test-helpers-so-type-serializer/BUILD.bazel new file mode 100644 index 000000000000..714250a907fb --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/BUILD.bazel @@ -0,0 +1,111 @@ +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 = "core-test-helpers-so-type-serializer" +PKG_REQUIRE_NAME = "@kbn/core-test-helpers-so-type-serializer" + +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", +] + +RUNTIME_DEPS = [ + "@npm//semver", + "//packages/kbn-std", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//@types/semver", + "//packages/kbn-std:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-common:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-server: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/core/test-helpers/core-test-helpers-so-type-serializer/README.md b/packages/core/test-helpers/core-test-helpers-so-type-serializer/README.md new file mode 100644 index 000000000000..d562272b9649 --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/README.md @@ -0,0 +1,3 @@ +# @kbn/core-test-helpers-so-type-serializer + +Utility package for savedObjects integration tests. diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/index.ts b/packages/core/test-helpers/core-test-helpers-so-type-serializer/index.ts new file mode 100644 index 000000000000..100acc28a200 --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { SavedObjectTypeMigrationInfo } from './src/extract_migration_info'; +export { extractMigrationInfo } from './src/extract_migration_info'; +export { getMigrationHash } from './src/get_migration_hash'; diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/jest.config.js b/packages/core/test-helpers/core-test-helpers-so-type-serializer/jest.config.js new file mode 100644 index 000000000000..66287aba24c2 --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/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/core/test-helpers/core-test-helpers-so-type-serializer'], +}; diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/kibana.jsonc b/packages/core/test-helpers/core-test-helpers-so-type-serializer/kibana.jsonc new file mode 100644 index 000000000000..4a4a765bbc51 --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-test-helpers-so-type-serializer", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/package.json b/packages/core/test-helpers/core-test-helpers-so-type-serializer/package.json new file mode 100644 index 000000000000..95de844e70ed --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-test-helpers-so-type-serializer", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.test.ts b/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.test.ts new file mode 100644 index 000000000000..47620aee90b3 --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { extractMigrationInfo } from './extract_migration_info'; + +const createType = (parts: Partial): SavedObjectsType => ({ + name: 'test-type', + hidden: false, + namespaceType: 'multiple', + mappings: { properties: {} }, + ...parts, +}); + +const dummyMigration = jest.fn(); +const dummySchema = schema.object({}); + +describe('extractMigrationInfo', () => { + describe('simple fields', () => { + it('returns the `name` from the SO type', () => { + const type = createType({ name: 'my-type' }); + const output = extractMigrationInfo(type); + expect(output.name).toEqual('my-type'); + }); + + it('returns the `namespaceType` from the SO type', () => { + const type = createType({ namespaceType: 'multiple-isolated' }); + const output = extractMigrationInfo(type); + expect(output.namespaceType).toEqual('multiple-isolated'); + }); + + it('returns the `convertToMultiNamespaceTypeVersion` from the SO type', () => { + const type = createType({ convertToMultiNamespaceTypeVersion: '6.6.6' }); + const output = extractMigrationInfo(type); + expect(output.convertToMultiNamespaceTypeVersion).toEqual('6.6.6'); + }); + + it('returns the `convertToAliasScript` from the SO type', () => { + const type = createType({ convertToAliasScript: 'some_value' }); + const output = extractMigrationInfo(type); + expect(output.convertToAliasScript).toEqual('some_value'); + }); + + it('returns true for `hasExcludeOnUpgrade` if the SO type specifies `excludeOnUpgrade`', () => { + expect( + extractMigrationInfo(createType({ excludeOnUpgrade: jest.fn() })).hasExcludeOnUpgrade + ).toEqual(true); + expect( + extractMigrationInfo(createType({ excludeOnUpgrade: undefined })).hasExcludeOnUpgrade + ).toEqual(false); + }); + }); + + describe('migrations', () => { + it('returns the versions with registered migrations, sorted asc', () => { + const type = createType({ + migrations: { + '8.3.3': dummyMigration, + '7.17.7': dummyMigration, + '8.0.2': dummyMigration, + }, + }); + + const output = extractMigrationInfo(type); + + expect(output.migrationVersions).toEqual(['7.17.7', '8.0.2', '8.3.3']); + }); + + it('supports migration provider functions', () => { + const type = createType({ + migrations: () => ({ + '8.3.3': dummyMigration, + '7.17.7': dummyMigration, + '8.0.2': dummyMigration, + }), + }); + + const output = extractMigrationInfo(type); + + expect(output.migrationVersions).toEqual(['7.17.7', '8.0.2', '8.3.3']); + }); + + it('returns an empty list when migrations are not defined', () => { + const type = createType({ + migrations: undefined, + }); + + const output = extractMigrationInfo(type); + + expect(output.migrationVersions).toEqual([]); + }); + }); + + describe('schemas', () => { + it('returns the versions with registered schemas, sorted asc', () => { + const type = createType({ + schemas: { + '8.3.2': dummySchema, + '7.15.2': dummySchema, + '8.1.2': dummySchema, + }, + }); + + const output = extractMigrationInfo(type); + + expect(output.schemaVersions).toEqual(['7.15.2', '8.1.2', '8.3.2']); + }); + + it('supports schema provider functions', () => { + const type = createType({ + schemas: () => ({ + '8.3.2': dummySchema, + '7.15.2': dummySchema, + '8.1.2': dummySchema, + }), + }); + + const output = extractMigrationInfo(type); + + expect(output.schemaVersions).toEqual(['7.15.2', '8.1.2', '8.3.2']); + }); + + it('returns an empty list when schemas are not defined', () => { + const type = createType({ + schemas: undefined, + }); + + const output = extractMigrationInfo(type); + + expect(output.schemaVersions).toEqual([]); + }); + }); + + describe('mappings', () => { + it('returns a flattened version of the mappings', () => { + const type = createType({ + mappings: { + dynamic: false, + properties: { + description: { type: 'text' }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + }); + const output = extractMigrationInfo(type); + expect(output.mappings).toEqual({ + dynamic: false, + 'properties.description.type': 'text', + 'properties.hits.doc_values': false, + 'properties.hits.index': false, + 'properties.hits.type': 'integer', + }); + }); + }); +}); diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts b/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.ts new file mode 100644 index 000000000000..c2f1c0b7aa9a --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/extract_migration_info.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 { compare as semverCompare } from 'semver'; +import { getFlattenedObject } from '@kbn/std'; +import type { SavedObjectsNamespaceType } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; + +export interface SavedObjectTypeMigrationInfo { + name: string; + namespaceType: SavedObjectsNamespaceType; + convertToAliasScript?: string; + convertToMultiNamespaceTypeVersion?: string; + migrationVersions: string[]; + schemaVersions: string[]; + mappings: Record; + hasExcludeOnUpgrade: boolean; +} + +/** + * Extract all migration-relevant informations bound to given type in a serializable format. + * + * @param soType + */ +export const extractMigrationInfo = (soType: SavedObjectsType): SavedObjectTypeMigrationInfo => { + const migrationMap = + typeof soType.migrations === 'function' ? soType.migrations() : soType.migrations; + const migrationVersions = Object.keys(migrationMap ?? {}); + migrationVersions.sort(semverCompare); + + const schemaMap = typeof soType.schemas === 'function' ? soType.schemas() : soType.schemas; + const schemaVersions = Object.keys(schemaMap ?? {}); + schemaVersions.sort(semverCompare); + + return { + name: soType.name, + namespaceType: soType.namespaceType, + convertToAliasScript: soType.convertToAliasScript, + convertToMultiNamespaceTypeVersion: soType.convertToMultiNamespaceTypeVersion, + migrationVersions, + schemaVersions, + mappings: getFlattenedObject(soType.mappings ?? {}), + hasExcludeOnUpgrade: !!soType.excludeOnUpgrade, + }; +}; diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/get_migration_hash.test.ts b/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/get_migration_hash.test.ts new file mode 100644 index 000000000000..2ac9e04172e8 --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/get_migration_hash.test.ts @@ -0,0 +1,336 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { getMigrationHash } from './get_migration_hash'; + +const createType = (parts: Partial = {}): SavedObjectsType => ({ + name: 'test-type', + hidden: false, + namespaceType: 'multiple', + mappings: { + dynamic: false, + properties: { + description: { type: 'text' }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + ...parts, +}); + +describe('getMigrationHash', () => { + it('returns the same hash for the exact same simple type', () => { + const type = createType(); + expect(getMigrationHash(type)).toEqual(getMigrationHash(type)); + }); + + describe('simple fields', () => { + it('returns different hashes if `name` changes', () => { + expect(getMigrationHash(createType({ name: 'typeA' }))).not.toEqual( + getMigrationHash(createType({ name: 'typeB' })) + ); + }); + it('returns different hashes if `namespaceType` changes', () => { + expect(getMigrationHash(createType({ namespaceType: 'single' }))).not.toEqual( + getMigrationHash(createType({ namespaceType: 'multiple' })) + ); + }); + it('returns different hashes if `convertToMultiNamespaceTypeVersion` changes', () => { + expect( + getMigrationHash(createType({ convertToMultiNamespaceTypeVersion: undefined })) + ).not.toEqual(getMigrationHash(createType({ convertToMultiNamespaceTypeVersion: '6.6.6' }))); + }); + it('returns different hashes if `convertToAliasScript` changes', () => { + expect(getMigrationHash(createType({ convertToAliasScript: undefined }))).not.toEqual( + getMigrationHash(createType({ convertToAliasScript: 'some_script' })) + ); + }); + it('returns different hashes if `excludeOnUpgrade` is defined or not', () => { + expect(getMigrationHash(createType({ excludeOnUpgrade: undefined }))).not.toEqual( + getMigrationHash(createType({ excludeOnUpgrade: jest.fn() })) + ); + }); + }); + + describe('migrations', () => { + it('returns same hash if same migration versions are registered', () => { + const typeA = createType({ + migrations: { + '7.17.1': jest.fn(), + '8.4.2': jest.fn(), + }, + }); + const typeB = createType({ + migrations: { + '7.17.1': jest.fn(), + '8.4.2': jest.fn(), + }, + }); + + expect(getMigrationHash(typeA)).toEqual(getMigrationHash(typeB)); + }); + + it('returns same hash if same migration versions are registered in different order', () => { + const typeA = createType({ + migrations: { + '9.1.3': jest.fn(), + '7.17.1': jest.fn(), + '8.4.2': jest.fn(), + }, + }); + const typeB = createType({ + migrations: { + '8.4.2': jest.fn(), + '9.1.3': jest.fn(), + '7.17.1': jest.fn(), + }, + }); + + expect(getMigrationHash(typeA)).toEqual(getMigrationHash(typeB)); + }); + + it('returns same hash if same migration versions are registered using record + function', () => { + const typeA = createType({ + migrations: { + '9.1.3': jest.fn(), + '7.17.1': jest.fn(), + '8.4.2': jest.fn(), + }, + }); + const typeB = createType({ + migrations: () => ({ + '8.4.2': jest.fn(), + '9.1.3': jest.fn(), + '7.17.1': jest.fn(), + }), + }); + + expect(getMigrationHash(typeA)).toEqual(getMigrationHash(typeB)); + }); + + it('returns different hashes if different migration versions are registered', () => { + const typeA = createType({ + migrations: { + '7.17.1': jest.fn(), + '8.4.2': jest.fn(), + }, + }); + const typeB = createType({ + migrations: { + '7.17.69': jest.fn(), + '42.0.0': jest.fn(), + }, + }); + + expect(getMigrationHash(typeA)).not.toEqual(getMigrationHash(typeB)); + }); + }); + describe('schemas', () => { + it('returns same hash if same schema versions are registered', () => { + const typeA = createType({ + schemas: { + '7.17.1': schema.object({}), + '8.4.2': schema.object({}), + }, + }); + const typeB = createType({ + schemas: { + '7.17.1': schema.object({}), + '8.4.2': schema.object({}), + }, + }); + + expect(getMigrationHash(typeA)).toEqual(getMigrationHash(typeB)); + }); + + it('returns same hash if same schema versions are registered in different order', () => { + const typeA = createType({ + schemas: { + '9.1.3': schema.object({}), + '7.17.1': schema.object({}), + '8.4.2': schema.object({}), + }, + }); + const typeB = createType({ + schemas: { + '8.4.2': schema.object({}), + '9.1.3': schema.object({}), + '7.17.1': schema.object({}), + }, + }); + + expect(getMigrationHash(typeA)).toEqual(getMigrationHash(typeB)); + }); + + it('returns same hash if same schema versions are registered using record + function', () => { + const typeA = createType({ + schemas: { + '9.1.3': schema.object({}), + '7.17.1': schema.object({}), + '8.4.2': schema.object({}), + }, + }); + const typeB = createType({ + schemas: () => ({ + '8.4.2': schema.object({}), + '9.1.3': schema.object({}), + '7.17.1': schema.object({}), + }), + }); + + expect(getMigrationHash(typeA)).toEqual(getMigrationHash(typeB)); + }); + + it('returns different hashes if different schema versions are registered', () => { + const typeA = createType({ + schemas: { + '7.17.1': schema.object({}), + '8.4.2': schema.object({}), + }, + }); + const typeB = createType({ + schemas: { + '7.17.69': schema.object({}), + '42.0.0': schema.object({}), + }, + }); + + expect(getMigrationHash(typeA)).not.toEqual(getMigrationHash(typeB)); + }); + }); + + describe('mappings', () => { + it('returns same hash for the same mappings', () => { + const typeA = createType({ + mappings: { + dynamic: false, + properties: { + description: { type: 'text' }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + }); + const typeB = createType({ + mappings: { + dynamic: false, + properties: { + description: { type: 'text' }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + }); + + expect(getMigrationHash(typeA)).toEqual(getMigrationHash(typeB)); + }); + + it('returns same hash for the same mappings in different order', () => { + const typeA = createType({ + mappings: { + dynamic: false, + properties: { + hits: { type: 'integer', index: false, doc_values: false }, + description: { type: 'text' }, + }, + }, + }); + const typeB = createType({ + mappings: { + properties: { + description: { type: 'text' }, + hits: { index: false, type: 'integer', doc_values: false }, + }, + dynamic: false, + }, + }); + + expect(getMigrationHash(typeA)).toEqual(getMigrationHash(typeB)); + }); + + it('returns different hashes for different mappings (removing nested property)', () => { + const typeA = createType({ + mappings: { + dynamic: false, + properties: { + description: { type: 'text' }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + }); + const typeB = createType({ + mappings: { + dynamic: false, + properties: { + description: { type: 'text' }, + hits: { type: 'integer', doc_values: false }, + }, + }, + }); + + expect(getMigrationHash(typeA)).not.toEqual(getMigrationHash(typeB)); + }); + + it('returns different hashes for different mappings (adding nested property)', () => { + const typeA = createType({ + mappings: { + dynamic: false, + properties: { + description: { type: 'text' }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + }); + const typeB = createType({ + mappings: { + dynamic: false, + properties: { + description: { type: 'text', boost: 42 }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + }); + + expect(getMigrationHash(typeA)).not.toEqual(getMigrationHash(typeB)); + }); + + it('returns different hashes for different mappings (removing top-level property)', () => { + const typeA = createType({ + mappings: { + dynamic: false, + properties: { + description: { type: 'text' }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + }); + const typeB = createType({ + mappings: { + properties: { + description: { type: 'text' }, + hits: { type: 'integer', index: false, doc_values: false }, + }, + }, + }); + + expect(getMigrationHash(typeA)).not.toEqual(getMigrationHash(typeB)); + }); + }); + + describe('ignored fields', () => { + it('returns same hash if `hidden` changes', () => { + expect(getMigrationHash(createType({ hidden: false }))).toEqual( + getMigrationHash(createType({ hidden: true })) + ); + }); + it('returns same hash if `management` changes', () => { + expect(getMigrationHash(createType({ management: undefined }))).toEqual( + getMigrationHash(createType({ management: { visibleInManagement: false } })) + ); + }); + }); +}); diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/get_migration_hash.ts b/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/get_migration_hash.ts new file mode 100644 index 000000000000..7e23ec35bb9e --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/src/get_migration_hash.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createHash } from 'crypto'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { extractMigrationInfo } from './extract_migration_info'; + +type SavedObjectTypeMigrationHash = string; + +export const getMigrationHash = (soType: SavedObjectsType): SavedObjectTypeMigrationHash => { + const migInfo = extractMigrationInfo(soType); + + const hash = createHash('sha1'); + + const hashParts = [ + migInfo.name, + migInfo.namespaceType, + migInfo.convertToAliasScript ?? 'none', + migInfo.hasExcludeOnUpgrade, + migInfo.convertToMultiNamespaceTypeVersion ?? 'none', + migInfo.migrationVersions.join(','), + migInfo.schemaVersions.join(','), + JSON.stringify(migInfo.mappings, Object.keys(migInfo.mappings).sort()), + ]; + const hashFeed = hashParts.join('-'); + + return hash.update(hashFeed).digest('hex'); +}; diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/tsconfig.json b/packages/core/test-helpers/core-test-helpers-so-type-serializer/tsconfig.json new file mode 100644 index 000000000000..71bb40fe57f3 --- /dev/null +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/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-apm-synthtrace/src/lib/apm/aggregators/service_metrics_aggregator.ts b/packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_metrics_aggregator.ts index 618c9e52b9f2..9ed6f805d154 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_metrics_aggregator.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/aggregators/service_metrics_aggregator.ts @@ -34,21 +34,19 @@ export type ServiceFields = Fields & | 'service.name' | 'service.version' | 'service.environment' - | 'transaction.type' > & Partial<{ _doc_count: number; transaction: { - duration: { - summary: { - min: number; - max: number; - sum: number; - value_count: number; - }; - }; failure_count: number; success_count: number; + type: string; + 'duration.summary': { + min: number; + max: number; + sum: number; + value_count: number; + }; }; }>; @@ -86,10 +84,10 @@ export class ServicMetricsAggregator implements StreamAggregator { }, }, failure_count: { - type: { type: 'long' }, + type: 'long', }, success_count: { - type: { type: 'long' }, + type: 'long', }, }, }, @@ -141,22 +139,24 @@ export class ServicMetricsAggregator implements StreamAggregator { } const state = this.state[key]; - state.count++; - - switch (event['event.outcome']) { - case 'failure': - state.failure_count++; - break; - case 'success': - state.success_count++; - break; - } const duration = Number(event['transaction.duration.us']); + if (duration >= 0) { + state.count++; + state.sum += duration; if (duration > state.max) state.max = duration; if (duration < state.min) state.min = Math.min(0, duration); + + switch (event['event.outcome']) { + case 'failure': + state.failure_count++; + break; + case 'success': + state.success_count++; + break; + } } }; @@ -197,18 +197,16 @@ export class ServicMetricsAggregator implements StreamAggregator { 'processor.event': 'metric', 'service.name': state['service.name'], 'service.environment': state['service.environment'], - 'transaction.type': state['transaction.type'], transaction: { - duration: { - summary: { - min: state.min, - max: state.max, - sum: state.sum, - value_count: state.count, - }, + 'duration.summary': { + min: state.min, + max: state.max, + sum: state.sum, + value_count: state.count, }, - failure_count: state.failure_count, success_count: state.success_count, + failure_count: state.failure_count, + type: state['transaction.type'] ?? 'request', }, }; } diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_span_destination_metrics.ts b/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_span_destination_metrics.ts index 4f04feb841dd..793f57a1a778 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_span_destination_metrics.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/processors/get_span_destination_metrics.ts @@ -18,6 +18,7 @@ export function getSpanDestinationMetrics(events: ApmFields[]) { 'service.environment', 'service.name', 'span.destination.service.resource', + 'span.name', ]); return metricsets.map((metricset) => { diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 445bf9458d45..b7a332694258 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -375,6 +375,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { es_connection: '', }, responseActions: `${SECURITY_SOLUTION_DOCS}response-actions.html`, + configureEndpointIntegrationPolicy: `${SECURITY_SOLUTION_DOCS}configure-endpoint-integration-policy.html`, }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, @@ -458,6 +459,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { 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`, + syntheticsProjectMonitors: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetic-run-tests.html#synthetic-monitor-choose-project`, }, 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 d9902a7b11de..16a80e7f7bf3 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -277,6 +277,7 @@ export interface DocLinks { }; readonly threatIntelInt: string; readonly responseActions: string; + readonly configureEndpointIntegrationPolicy: string; }; readonly query: { readonly eql: string; @@ -340,6 +341,7 @@ export interface DocLinks { userExperience: string; createAlerts: string; syntheticsCommandReference: string; + syntheticsProjectMonitors: string; }>; readonly alerting: Record; readonly maps: Readonly<{ diff --git a/packages/kbn-es-query/index.ts b/packages/kbn-es-query/index.ts index 7495d37fc990..43c660544bc9 100644 --- a/packages/kbn-es-query/index.ts +++ b/packages/kbn-es-query/index.ts @@ -67,6 +67,7 @@ export { buildEmptyFilter, buildExistsFilter, buildFilter, + buildCombinedFilter, buildPhraseFilter, buildPhrasesFilter, buildQueryFilter, @@ -89,6 +90,7 @@ export { isFilterPinned, isFilters, isMatchAllFilter, + isCombinedFilter, isPhraseFilter, isPhrasesFilter, isQueryStringFilter, diff --git a/packages/kbn-es-query/src/es_query/from_filters.ts b/packages/kbn-es-query/src/es_query/from_filters.ts index 2200648a52c4..9e2599cc6c70 100644 --- a/packages/kbn-es-query/src/es_query/from_filters.ts +++ b/packages/kbn-es-query/src/es_query/from_filters.ts @@ -13,6 +13,7 @@ import { filterMatchesIndex } from './filter_matches_index'; import { Filter, cleanFilter, isFilterDisabled } from '../filters'; import { BoolQuery, DataViewBase } from './types'; import { handleNestedFilter } from './handle_nested_filter'; +import { handleCombinedFilter } from './handle_combined_filter'; /** * Create a filter that can be reversed for filters with negate set @@ -66,10 +67,11 @@ export interface EsQueryFiltersConfig { export const buildQueryFromFilters = ( inputFilters: Filter[] = [], inputDataViews: DataViewBase | DataViewBase[] | undefined, - { ignoreFilterIfFieldNotInIndex = false, nestedIgnoreUnmapped }: EsQueryFiltersConfig = { + options: EsQueryFiltersConfig = { ignoreFilterIfFieldNotInIndex: false, } ): BoolQuery => { + const { ignoreFilterIfFieldNotInIndex = false, nestedIgnoreUnmapped } = options; const filters = inputFilters.filter((filter) => filter && !isFilterDisabled(filter)); const indexPatterns = Array.isArray(inputDataViews) ? inputDataViews : [inputDataViews]; @@ -92,6 +94,7 @@ export const buildQueryFromFilters = ( ignoreUnmapped: nestedIgnoreUnmapped, }); }) + .map((filter) => handleCombinedFilter(filter, inputDataViews, options)) .map(cleanFilter) .map(translateToQuery); }; diff --git a/packages/kbn-es-query/src/es_query/handle_combined_filter.test.ts b/packages/kbn-es-query/src/es_query/handle_combined_filter.test.ts new file mode 100644 index 000000000000..0cac2cb12f60 --- /dev/null +++ b/packages/kbn-es-query/src/es_query/handle_combined_filter.test.ts @@ -0,0 +1,610 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fields } from '../filters/stubs'; +import { DataViewBase } from './types'; +import { handleCombinedFilter } from './handle_combined_filter'; +import { + buildExistsFilter, + buildCombinedFilter, + buildPhraseFilter, + buildPhrasesFilter, + buildRangeFilter, +} from '../filters'; + +describe('#handleCombinedFilter', function () { + const indexPattern: DataViewBase = { + id: 'logstash-*', + fields, + title: 'dataView', + }; + + const getField = (fieldName: string) => { + const field = fields.find(({ name }) => fieldName === name); + if (!field) throw new Error(`field ${name} does not exist`); + return field; + }; + + it('Handles an empty list of filters', () => { + const filter = buildCombinedFilter([]); + const result = handleCombinedFilter(filter); + expect(result.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [], + }, + } + `); + }); + + it('Handles a simple list of filters', () => { + const filters = [ + buildPhraseFilter(getField('extension'), 'value', indexPattern), + buildRangeFilter(getField('bytes'), { gte: 10 }, indexPattern), + buildExistsFilter(getField('machine.os'), indexPattern), + ]; + const filter = buildCombinedFilter(filters); + const result = handleCombinedFilter(filter); + expect(result.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_phrase": Object { + "extension": "value", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "bytes": Object { + "gte": 10, + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "machine.os", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ], + }, + } + `); + }); + + it('Handles a combination of filters and filter arrays', () => { + const filters = [ + buildPhraseFilter(getField('extension'), 'value', indexPattern), + [ + buildRangeFilter(getField('bytes'), { gte: 10 }, indexPattern), + buildExistsFilter(getField('machine.os'), indexPattern), + ], + ]; + const filter = buildCombinedFilter(filters); + const result = handleCombinedFilter(filter); + expect(result.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_phrase": Object { + "extension": "value", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "bytes": Object { + "gte": 10, + }, + }, + }, + Object { + "exists": Object { + "field": "machine.os", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ], + }, + } + `); + }); + + it('Handles nested COMBINED filters', () => { + const nestedCombinedFilter = buildCombinedFilter([ + buildPhraseFilter(getField('machine.os'), 'value', indexPattern), + buildPhraseFilter(getField('extension'), 'value', indexPattern), + ]); + const filters = [ + buildPhraseFilter(getField('extension'), 'value2', indexPattern), + nestedCombinedFilter, + buildRangeFilter(getField('bytes'), { gte: 10 }, indexPattern), + buildExistsFilter(getField('machine.os.raw'), indexPattern), + ]; + const filter = buildCombinedFilter(filters); + const result = handleCombinedFilter(filter); + expect(result.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_phrase": Object { + "extension": "value2", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_phrase": Object { + "machine.os": "value", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "match_phrase": Object { + "extension": "value", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ], + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "bytes": Object { + "gte": 10, + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "machine.os.raw", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ], + }, + } + `); + }); + + it('Handles negated sub-filters', () => { + const negatedFilter = buildPhrasesFilter(getField('extension'), ['tar', 'gz'], indexPattern); + negatedFilter.meta.negate = true; + + const filters = [ + [negatedFilter, buildPhraseFilter(getField('extension'), 'value', indexPattern)], + buildRangeFilter(getField('bytes'), { gte: 10 }, indexPattern), + buildExistsFilter(getField('machine.os'), indexPattern), + ]; + const filter = buildCombinedFilter(filters); + const result = handleCombinedFilter(filter); + expect(result.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_phrase": Object { + "extension": "value", + }, + }, + ], + "must": Array [], + "must_not": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "extension": "tar", + }, + }, + Object { + "match_phrase": Object { + "extension": "gz", + }, + }, + ], + }, + }, + ], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "bytes": Object { + "gte": 10, + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "machine.os", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ], + }, + } + `); + }); + + it('Handles disabled filters within a filter array', () => { + const disabledFilter = buildPhraseFilter(getField('ssl'), false, indexPattern); + disabledFilter.meta.disabled = true; + const filters = [ + buildPhraseFilter(getField('extension'), 'value', indexPattern), + [disabledFilter, buildRangeFilter(getField('bytes'), { gte: 10 }, indexPattern)], + buildExistsFilter(getField('machine.os'), indexPattern), + ]; + const filter = buildCombinedFilter(filters); + const result = handleCombinedFilter(filter); + expect(result.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_phrase": Object { + "extension": "value", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "bytes": Object { + "gte": 10, + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "machine.os", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ], + }, + } + `); + }); + + it('Handles complex-nested filters with ANDs and ORs', () => { + const filters = [ + [ + buildPhrasesFilter(getField('extension'), ['tar', 'gz'], indexPattern), + buildPhraseFilter(getField('ssl'), false, indexPattern), + buildCombinedFilter([ + buildPhraseFilter(getField('extension'), 'value', indexPattern), + buildRangeFilter(getField('bytes'), { gte: 10 }, indexPattern), + ]), + buildExistsFilter(getField('machine.os'), indexPattern), + ], + buildPhrasesFilter(getField('machine.os.keyword'), ['foo', 'bar'], indexPattern), + ]; + const filter = buildCombinedFilter(filters); + const result = handleCombinedFilter(filter); + expect(result.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "extension": "tar", + }, + }, + Object { + "match_phrase": Object { + "extension": "gz", + }, + }, + ], + }, + }, + Object { + "match_phrase": Object { + "ssl": false, + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "match_phrase": Object { + "extension": "value", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "bytes": Object { + "gte": 10, + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ], + }, + }, + Object { + "exists": Object { + "field": "machine.os", + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "machine.os.keyword": "foo", + }, + }, + Object { + "match_phrase": Object { + "machine.os.keyword": "bar", + }, + }, + ], + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + ], + }, + } + `); + }); + + it('Preserves filter properties', () => { + const filters = [ + buildPhraseFilter(getField('extension'), 'value', indexPattern), + buildRangeFilter(getField('bytes'), { gte: 10 }, indexPattern), + buildExistsFilter(getField('machine.os'), indexPattern), + ]; + const filter = buildCombinedFilter(filters); + const { query, ...rest } = handleCombinedFilter(filter); + expect(rest).toMatchInlineSnapshot(` + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": undefined, + "negate": false, + "params": Array [ + Object { + "meta": Object { + "index": "logstash-*", + }, + "query": Object { + "match_phrase": Object { + "extension": "value", + }, + }, + }, + Object { + "meta": Object { + "field": "bytes", + "index": "logstash-*", + "params": Object {}, + }, + "query": Object { + "range": Object { + "bytes": Object { + "gte": 10, + }, + }, + }, + }, + Object { + "meta": Object { + "index": "logstash-*", + }, + "query": Object { + "exists": Object { + "field": "machine.os", + }, + }, + }, + ], + "type": "combined", + }, + } + `); + }); +}); diff --git a/packages/kbn-es-query/src/es_query/handle_combined_filter.ts b/packages/kbn-es-query/src/es_query/handle_combined_filter.ts new file mode 100644 index 000000000000..a9daf3fc4f33 --- /dev/null +++ b/packages/kbn-es-query/src/es_query/handle_combined_filter.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Filter, FilterItem, isCombinedFilter } from '../filters'; +import { DataViewBase } from './types'; +import { buildQueryFromFilters, EsQueryFiltersConfig } from './from_filters'; + +/** @internal */ +export const handleCombinedFilter = ( + filter: Filter, + inputDataViews?: DataViewBase | DataViewBase[], + options: EsQueryFiltersConfig = {} +): Filter => { + if (!isCombinedFilter(filter)) return filter; + const { params } = filter.meta; + const should = params.map((subFilter) => { + const subFilters = Array.isArray(subFilter) ? subFilter : [subFilter]; + return { bool: buildQueryFromFilters(flattenFilters(subFilters), inputDataViews, options) }; + }); + return { + ...filter, + query: { + bool: { + should, + minimum_should_match: 1, + }, + }, + }; +}; + +function flattenFilters(filters: FilterItem[]): Filter[] { + return filters.reduce((result, filter) => { + if (Array.isArray(filter)) return [...result, ...flattenFilters(filter)]; + return [...result, filter]; + }, []); +} diff --git a/packages/kbn-es-query/src/filters/build_filters/combined_filter.ts b/packages/kbn-es-query/src/filters/build_filters/combined_filter.ts new file mode 100644 index 000000000000..4054f25ce45f --- /dev/null +++ b/packages/kbn-es-query/src/filters/build_filters/combined_filter.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Filter, FilterMeta, FILTERS } from './types'; +import { buildEmptyFilter } from './build_empty_filter'; + +/** + * Each item in an COMBINED filter may represent either one filter (to be ORed) or an array of filters (ANDed together before + * becoming part of the OR clause). + * @public + */ +export type FilterItem = Filter | FilterItem[]; + +/** + * @public + */ +export interface CombinedFilterMeta extends FilterMeta { + type: typeof FILTERS.COMBINED; + params: FilterItem[]; +} + +/** + * @public + */ +export interface CombinedFilter extends Filter { + meta: CombinedFilterMeta; +} + +/** + * @public + */ +export function isCombinedFilter(filter: Filter): filter is CombinedFilter { + return filter?.meta?.type === FILTERS.COMBINED; +} + +/** + * Builds an COMBINED filter. An COMBINED filter is a filter with multiple sub-filters. Each sub-filter (FilterItem) represents a + * condition. + * @param filters An array of CombinedFilterItem + * @public + */ +export function buildCombinedFilter(filters: FilterItem[]): CombinedFilter { + const filter = buildEmptyFilter(false); + return { + ...filter, + meta: { + ...filter.meta, + type: FILTERS.COMBINED, + params: filters, + }, + }; +} diff --git a/packages/kbn-es-query/src/filters/build_filters/index.ts b/packages/kbn-es-query/src/filters/build_filters/index.ts index d9d4bbb82aeb..0b80363703da 100644 --- a/packages/kbn-es-query/src/filters/build_filters/index.ts +++ b/packages/kbn-es-query/src/filters/build_filters/index.ts @@ -14,6 +14,7 @@ export * from './exists_filter'; export * from './get_filter_field'; export * from './get_filter_params'; export * from './match_all_filter'; +export * from './combined_filter'; export * from './phrase_filter'; export * from './phrases_filter'; export * from './query_string_filter'; diff --git a/packages/kbn-es-query/src/filters/build_filters/types.ts b/packages/kbn-es-query/src/filters/build_filters/types.ts index 5e920d11bcab..27140c9a72e9 100644 --- a/packages/kbn-es-query/src/filters/build_filters/types.ts +++ b/packages/kbn-es-query/src/filters/build_filters/types.ts @@ -37,6 +37,7 @@ export enum FILTERS { RANGE = 'range', RANGE_FROM_VALUE = 'range_from_value', SPATIAL_FILTER = 'spatial_filter', + COMBINED = 'combined', } /** diff --git a/packages/kbn-es-query/src/filters/index.ts b/packages/kbn-es-query/src/filters/index.ts index 820559d5f906..93efb9b1cd61 100644 --- a/packages/kbn-es-query/src/filters/index.ts +++ b/packages/kbn-es-query/src/filters/index.ts @@ -34,6 +34,8 @@ export { export { isExistsFilter, isMatchAllFilter, + buildCombinedFilter, + isCombinedFilter, isPhraseFilter, isPhrasesFilter, isRangeFilter, @@ -75,6 +77,9 @@ export type { CustomFilter, RangeFilterParams, QueryStringFilter, + CombinedFilter, + CombinedFilterMeta, + FilterItem, } from './build_filters'; export { FilterStateStore, FILTERS } from './build_filters/types'; diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index e6b6494e68a5..672c25d4e8fa 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -34,6 +34,7 @@ const ALERT_REASON = `${ALERT_NAMESPACE}.reason` as const; const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const; const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const; const ALERT_START = `${ALERT_NAMESPACE}.start` as const; +const ALERT_TIME_RANGE = `${ALERT_NAMESPACE}.time_range` as const; const ALERT_STATUS = `${ALERT_NAMESPACE}.status` as const; const ALERT_SYSTEM_STATUS = `${ALERT_NAMESPACE}.system_status` as const; const ALERT_UUID = `${ALERT_NAMESPACE}.uuid` as const; @@ -126,6 +127,7 @@ const fields = { ALERT_RULE_UPDATED_BY, ALERT_RULE_VERSION, ALERT_START, + ALERT_TIME_RANGE, ALERT_SEVERITY, ALERT_STATUS, ALERT_SYSTEM_STATUS, @@ -183,6 +185,7 @@ export { ALERT_RULE_VERSION, ALERT_SEVERITY, ALERT_START, + ALERT_TIME_RANGE, ALERT_SYSTEM_STATUS, ALERT_UUID, ECS_VERSION, diff --git a/packages/kbn-securitysolution-list-utils/src/helpers/index.ts b/packages/kbn-securitysolution-list-utils/src/helpers/index.ts index adbffa3c728e..6f4bc7d51052 100644 --- a/packages/kbn-securitysolution-list-utils/src/helpers/index.ts +++ b/packages/kbn-securitysolution-list-utils/src/helpers/index.ts @@ -138,13 +138,13 @@ export const getNewExceptionItem = ({ namespaceType, ruleName, }: { - listId: string; - namespaceType: NamespaceType; + listId: string | undefined; + namespaceType: NamespaceType | undefined; ruleName: string; }): CreateExceptionListItemBuilderSchema => { return { comments: [], - description: `${ruleName} - exception list item`, + description: 'Exception list item', entries: addIdToEntries([ { field: '', diff --git a/packages/kbn-securitysolution-list-utils/src/types/index.ts b/packages/kbn-securitysolution-list-utils/src/types/index.ts index 24de9d1b1d75..13b62fac657a 100644 --- a/packages/kbn-securitysolution-list-utils/src/types/index.ts +++ b/packages/kbn-securitysolution-list-utils/src/types/index.ts @@ -9,6 +9,7 @@ import { DataViewFieldBase } from '@kbn/es-query'; import type { CreateExceptionListItemSchema, + CreateRuleExceptionListItemSchema, Entry, EntryExists, EntryMatch, @@ -18,6 +19,7 @@ import type { ExceptionListItemSchema, ListOperatorEnum as OperatorEnum, ListOperatorTypeEnum as OperatorTypeEnum, + NamespaceType, } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_LIST_NAMESPACE, @@ -93,16 +95,23 @@ export type ExceptionListItemBuilderSchema = Omit & { meta: { temporaryUuid: string }; entries: BuilderEntry[]; + list_id: string | undefined; + namespace_type: NamespaceType | undefined; }; export type ExceptionsBuilderExceptionItem = | ExceptionListItemBuilderSchema | CreateExceptionListItemBuilderSchema; +export type ExceptionsBuilderReturnExceptionItem = + | ExceptionListItemSchema + | CreateExceptionListItemSchema + | CreateRuleExceptionListItemSchema; + export const exceptionListSavedObjectType = EXCEPTION_LIST_NAMESPACE; export const exceptionListAgnosticSavedObjectType = EXCEPTION_LIST_NAMESPACE_AGNOSTIC; export type SavedObjectType = diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index 24cd48235293..33360bf82ef0 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -254,6 +254,7 @@ export class KbnClientSavedObjects { 'epm-packages', 'epm-packages-assets', 'fleet-preconfiguration-deletion-record', + 'fleet-fleet-server-host', ]; const newOptions = { types, space: options?.space }; diff --git a/packages/kbn-tinymath/src/functions/defaults.js b/packages/kbn-tinymath/src/functions/defaults.js new file mode 100644 index 000000000000..4f2d276626d9 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/defaults.js @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Returns the default provided value when the first value if null. If at least one array is passed into the function, the function will be applied index-wise to each element. + * @param {(any|any[])} a array of any values + * @param {(any)} b a value to use as fallback. + * @return {(any|any[])} The `a` value if not null, `b` otherwise. Returns an array where each element is default to `b` when null, or kept the original value if `a` is an array. + * + * @example + * defaults(null, 1) // returns 1 + * defaults([3, null, 5], 1) // returns [3, 1, 5] + * defaults(5, 1) // returns 5 + */ + +module.exports = { defaults }; + +function defaults(a, b) { + if (Array.isArray(a)) { + return a.map((v) => (v == null ? b : v)); + } + return a == null ? b : a; +} + +defaults.skipNumberValidation = true; diff --git a/packages/kbn-tinymath/src/functions/index.js b/packages/kbn-tinymath/src/functions/index.js index eb58a4d56b56..37c2a13c41cf 100644 --- a/packages/kbn-tinymath/src/functions/index.js +++ b/packages/kbn-tinymath/src/functions/index.js @@ -14,6 +14,7 @@ const { clamp } = require('./clamp'); const { cos } = require('./cos'); const { count } = require('./count'); const { cube } = require('./cube'); +const { defaults } = require('./defaults'); const { degtorad } = require('./degtorad'); const { divide } = require('./divide'); const { exp } = require('./exp'); @@ -56,6 +57,7 @@ module.exports = { count, cube, degtorad, + defaults, divide, exp, first, diff --git a/packages/kbn-tinymath/test/functions/defaults.test.js b/packages/kbn-tinymath/test/functions/defaults.test.js new file mode 100644 index 000000000000..4490ba6787f2 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/defaults.test.js @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const { defaults } = require('../../src/functions/defaults'); + +describe('Defaults', () => { + it('number, number', () => { + expect(defaults(10, 2)).toEqual(10); + }); + + it('null, number', () => { + expect(defaults(null, 2)).toEqual(2); + }); + + it('number, null', () => { + expect(defaults(2, null)).toEqual(2); + }); + + it('array, number', () => { + expect(defaults([10, 20, 30, 40], 10)).toEqual([10, 20, 30, 40]); + }); + + it('arrays with null, number', () => { + expect(defaults([null, 20, 30, null], 10)).toEqual([10, 20, 30, 10]); + }); + + it('empty array, number', () => { + expect(defaults([], 10)).toEqual([]); + }); + + it('skips number validation', () => { + expect(defaults).toHaveProperty('skipNumberValidation', true); + }); +}); diff --git a/renovate.json b/renovate.json index 4075b2452bea..bdaf373932fa 100644 --- a/renovate.json +++ b/renovate.json @@ -210,7 +210,7 @@ }, { "groupName": "Profiling", - "matchPackageNames": ["fnv-plus", "peggy", "@types/dagre", "@types/fnv-plus"], + "matchPackageNames": ["peggy", "@types/dagre"], "reviewers": ["team:profiling-ui"], "matchBaseBranches": ["main"], "labels": ["release_note:skip", "backport:skip"], diff --git a/src/core/server/docs/kib_core_reviewing_so_type_pr.mdx b/src/core/server/docs/kib_core_reviewing_so_type_pr.mdx new file mode 100644 index 000000000000..95dc46610954 --- /dev/null +++ b/src/core/server/docs/kib_core_reviewing_so_type_pr.mdx @@ -0,0 +1,84 @@ +--- +id: kibCoreReviewingSoPr +slug: /kibana-dev-docs/review/reviewing-so-pr +title: Reviewing SavedObject PRs +description: How to review PRs that changes savedObjects registration +date: 2022-09-30 +tags: ['kibana','dev', 'contributor', 'api docs'] +--- + +# Reviewing PRs that change Saved Object types + +## How does automatic review assignment work when SO types are changed? + +PRs modifying / adding / deleting any SO type registration will be flagged by the integration +test located at `src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts`. + +This test will fail any time a change is performed on an SO type registration that could risk having an impact on +upgrades/migrations, and will force the PR's author to update the test's snapshot, which will trigger a review +from the Core team. + +## What and how to review? + +Reviews will be triggered when one, or more, of these scenarios occur: +- a new type was added (registered) +- a type was removed (is no longer registered) +- a new migration function was added +- a mapping change was performed +- a new validation schema was added +- an `excludeOnUpgrade` function was added or removed + +Note: reviews will **not** automatically be triggered in these scenarios: +- an existing migration function is changed +- an existing validation schema is changed +- an existing `excludeOnUpgrade` function is changed + +### A new type was added + +We have another integration test detecting this scenario (`src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts`) + +In that scenario, we should: +- check the initial mappings of the type to spot potential issues: + - fields being defined explicitly in the mappings but not directly used for search + - overall amount of fields is high + - use of `dynamic: true` (this can lead to mapping explosions) +- check if the type is registered as `hidden: true` and encourage to do so otherwise. + - this avoids polluting the global SO HTTP APIs with another type, and instead requires plugin developers to build + their own HTTP APIs to access this type of SO if they truly need to. + +### A type was removed + +The integration test mentioned in the previous section also detects those scenarios. + +Here, we need to check: +- that `REMOVED_TYPES` (`packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts`) was properly updated + - (but, again, the integration test will fail otherwise) +- that no other existing SO types may be referencing this type + - can't really be automated, will likely need to ask the owning team directly + +### A new migration function was added + +- Review the migration function + - owners are supposed to do it, but an additional review never hurts + - make sure they are typing their migration function using `SavedObjectMigrationFn` and supplying arguments for the generics + e.g. `SavedObjectMigrationFn`. +- Make sure that the migration function is properly tested + - by unit tests + - and, ideally, by integration tests using 'real' data +- If the migration function is moving/creating/deleting/mutating fields, make sure that the type's mappings and/or schemas were updated accordingly +- Please refer to [the migration section of our testing docs](https://github.com/elastic/kibana/blob/main/dev_docs/tutorials/testing_plugins.mdx#L796) for more details + +### A mapping change was performed + +- Make sure a migration function was added to reflect the changes: + - If a field was removed, ensure the migration function always removes the field from all documents + - If a field type was changed from e.g. `text` to `long`, make sure the migration function guarantees that all documents have compatible fields + - If a text type was changed to a keyword, make sure the text won't exceed Elasticsearch's 32k keyword length limit by e.g. specifying: `ignore_above: 256` +- If the migration function is present, refer to previous section. +- If the type is registering validation schemas, make sure a new schema was added reflecting the changes to the model. + +### A new validation schema was added + +- Make sure the associated mapping changes were performed, and that a migration function was added accordingly. +- Ideally schemas are validated with unit tests as well, especially for more complex ones. +- Refer to prior sections to see what to check. \ No newline at end of file diff --git a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts new file mode 100644 index 000000000000..e7d02da09889 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.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 type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { getMigrationHash } from '@kbn/core-test-helpers-so-type-serializer'; +import { Root } from '../../../root'; +import * as kbnTestServer from '../../../../test_helpers/kbn_server'; + +describe('checking migration metadata changes on all registered SO types', () => { + let esServer: kbnTestServer.TestElasticsearchUtils; + let root: Root; + let typeRegistry: ISavedObjectTypeRegistry; + + beforeAll(async () => { + const { startES } = kbnTestServer.createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + }); + + esServer = await startES(); + root = kbnTestServer.createRootWithCorePlugins({}, { oss: false }); + await root.preboot(); + await root.setup(); + const coreStart = await root.start(); + typeRegistry = coreStart.savedObjects.getTypeRegistry(); + }); + + afterAll(async () => { + if (root) { + await root.shutdown(); + } + if (esServer) { + await esServer.stop(); + } + }); + + // This test is meant to fail when any change is made in registered types that could potentially impact the SO migration. + // Just update the snapshot by running this test file via jest_integration with `-u` and push the update. + // The intent is to trigger a code review from the Core team to review the SO type changes. + it('detecting migration related changes in registered types', () => { + const allTypes = typeRegistry.getAllTypes(); + + const hashMap = allTypes.reduce((map, type) => { + map[type.name] = getMigrationHash(type); + return map; + }, {} as Record); + + expect(hashMap).toMatchInlineSnapshot(` + Object { + "action": "7858e6d5a9f231bf23f6f2e57328eb0095b26735", + "action_task_params": "bbd38cbfd74bf6713586fe078e3fa92db2234299", + "alert": "48461f3375d9ba22882ea23a318b62a5b0921a9b", + "api_key_pending_invalidation": "9b4bc1235337da9a87ef05a1d1f4858b2a3b77c6", + "apm-indices": "ceb0870f3a74e2ffc3a1cd3a3c73af76baca0999", + "apm-server-schema": "2bfd2998d3873872e1366458ce553def85418f91", + "apm-service-group": "07ecbf25ee4828d2b686abc98656b6665831d1a0", + "apm-telemetry": "abaa1e9469e6e0bad76938309f0ac4c66b528d58", + "app_search_telemetry": "7fc4fc08852bf0924ee29942bb394fda9aa8954d", + "application_usage_daily": "6e645e0b60ef3af2e8fde80963c2a4f09a190d61", + "application_usage_totals": "b2af3577dcd50bfae492b166a7804f69e2cc41dc", + "canvas-element": "5f32b99ba6ff9c1f17cc093591b975be65a27b9b", + "canvas-workpad": "b60252414fb6159a14f9febf98dbe41e5a8bf199", + "canvas-workpad-template": "c371cad0a8d61385f4782cab9a9063d3cf241ee0", + "cases": "7ff5ce930146a2d6fc8fbf536ce2ee16e9df296f", + "cases-comments": "8cfbad4ede637305eb6fb79db680f03dc0ce5ec4", + "cases-configure": "1afc414f5563a36e4612fa269193d3ed7277c7bd", + "cases-connector-mappings": "4b16d440af966e5d6e0fa33368bfa15d987a4b69", + "cases-telemetry": "16e261e7378a72acd0806f18df92525dd1da4f37", + "cases-user-actions": "3973dfcaacbe6ae147d7331699cfc25d2a27ca30", + "config": "e3f0408976dbdd453641f5699927b28b188f6b8c", + "connector_token": "fa5301aa5a2914795d3b1b82d0a49939444009da", + "core-usage-stats": "f40a213da2c597b0de94e364a4326a5a1baa4ca9", + "csp-rule-template": "3679c5f2431da8153878db79c78a4e695357fb61", + "csp_rule": "d2bb53ea5d2bdfba1a835ad8956dfcd2b2c32e19", + "dashboard": "0b0842b6aa40c125d64233fd81cee11080580dc2", + "endpoint:user-artifact": "f94c250a52b30d0a2d32635f8b4c5bdabd1e25c0", + "endpoint:user-artifact-manifest": "8c14d49a385d5d1307d956aa743ec78de0b2be88", + "enterprise_search_telemetry": "fafcc8318528d34f721c42d1270787c52565bad5", + "epm-packages": "c4c39f20d6bcfff40994813ee0f2bab01d34b646", + "epm-packages-assets": "9fd3d6726ac77369249e9a973902c2cd615fc771", + "event_loop_delays_daily": "d2ed39cf669577d90921c176499908b4943fb7bd", + "exception-list": "fe8cc004fd2742177cdb9300f4a67689463faf9c", + "exception-list-agnostic": "49fae8fcd1967cc4be45ba2a2c66c4afbc1e341b", + "file": "280f28bd48b3ad1f1a9f84c6c0ae6dd5ed1179da", + "file-upload-usage-collection-telemetry": "8478924cf0057bd90df737155b364f98d05420a5", + "fileShare": "3f88784b041bb8728a7f40763a08981828799a75", + "fleet-fleet-server-host": "f00ca963f1bee868806319789cdc33f1f53a97e2", + "fleet-preconfiguration-deletion-record": "7b28f200513c28ae774f1b7d7d7906954e3c6e16", + "graph-workspace": "3342f2cd561afdde8f42f5fb284bf550dee8ebb5", + "guided-onboarding-guide-state": "561db8d481b131a2bbf46b1e534d6ce960255135", + "index-pattern": "48e77ca393c254e93256f11a7cdc0232dd754c08", + "infrastructure-monitoring-log-view": "e2c78c1076bd35e57d7c5fa1b410e5c126d12327", + "infrastructure-ui-source": "7c8dbbc0a608911f1b683a944f4a65383f6153ed", + "ingest-agent-policies": "5d728f483dc3b14dcfa6bbad95c2024d2da68890", + "ingest-download-sources": "1e69dabd6db5e320fe08c5bda8f35f29bafc6b54", + "ingest-outputs": "29b867bf7bfd28b1e17c84697dce5c6d078f9705", + "ingest-package-policies": "e8707a8c7821ea085e67c2d213e24efa56307393", + "ingest_manager_settings": "bb71f20e36a9ac3a2e46d9345e2caa96e7bf8c22", + "inventory-view": "bc2bd1e7ec7c186159447ab228d269f22bd39056", + "kql-telemetry": "29544cd7d3b767c5399878efae6bd724d24c03fd", + "legacy-url-alias": "7172dfd54f2e0c89fe263fd7095519b2d826a930", + "lens": "08769c789ad6d1b8a4d0cffebc9d9bb08bf01ad9", + "lens-ui-telemetry": "df2844565c9e18fed2bdb1f6cc3aadd58cf1e45b", + "map": "00ca6c4cf46ae59f70f1436262eb9f457b45eb14", + "maps-telemetry": "5adbde35bd50ec2b8e9ea5b96d4d9f886e31ecfb", + "metrics-explorer-view": "09e56993352b8ee678e88f71e4410d9aeee72f3a", + "ml-job": "2836da98a81bd220db61c0549e8e28da7a876cb2", + "ml-module": "95055522c8406afa67a554690a43506f6c040744", + "ml-trained-model": "e39dd10b2da827e194ddcaaf3db141ad1daf0201", + "monitoring-telemetry": "af508cea8e22edaa909e462069390650fbbf01b7", + "osquery-manager-usage-metric": "fbe3cbea25a96e2ca522ca436878e0162c94dcc2", + "osquery-pack": "afb3b46c5e23fc24ad438e9c4317ff37e4e5164a", + "osquery-pack-asset": "32421669c87c49dfabd4d3957f044e5eb7f7fb20", + "osquery-saved-query": "7b213b4b7a3e59350e99c50e8df9948662ed493a", + "query": "4640ef356321500a678869f24117b7091a911cb6", + "sample-data-telemetry": "8b10336d9efae6f3d5593c4cc89fb4abcdf84e04", + "search": "e7ba25ea37cb36b622db42c9590c6d8dfc838801", + "search-session": "ba383309da68a15be3765977f7a44c84f0ec7964", + "search-telemetry": "beb3fc25488c753f2a6dcff1845d667558712b66", + "security-rule": "e0dfdba5d66139d0300723b2e6672993cd4a11f3", + "security-solution-signals-migration": "e65933e32926e0ca385415bd44fc6da0b6d3d419", + "siem-detection-engine-rule-actions": "d4b5934c0c0e4ccdf509a41000eb0bee07be0c28", + "siem-detection-engine-rule-execution-info": "b92d51db7b7d591758d3e85892a91064aff01ff8", + "siem-ui-timeline": "95474f10662802e2f9ea068b45bf69212a2f5842", + "siem-ui-timeline-note": "08c71dc0b8b8018a67e80beb4659a078404c223d", + "siem-ui-timeline-pinned-event": "e2697b38751506c7fce6e8b7207a830483dc4283", + "space": "c4a0acce1bd4b9cce85154f2a350624a53111c59", + "spaces-usage-stats": "922d3235bbf519e3fb3b260e27248b1df8249b79", + "synthetics-monitor": "cffb4dfe9e0a36755a226d5cf983c21aac2b5b1e", + "synthetics-privates-locations": "dd00385f4a27ef062c3e57312eeb3799872fa4af", + "tag": "39413f4578cc2128c9a0fda97d0acd1c8862c47a", + "task": "ef53d0f070bd54957b8fe22fae3b1ff208913f76", + "telemetry": "9142dc5f18123fb6e6a9083db04e5becbfde94fd", + "ui-metric": "2fb66ccdee2d1fad52547964421629c5a485c38f", + "upgrade-assistant-ml-upgrade-operation": "408120d386c04ab25fe64a03937597aa0438c10d", + "upgrade-assistant-reindex-operation": "d9e18b3d9578ecabf09a297296dcf7e36b2481fd", + "upgrade-assistant-telemetry": "a0c80933a9f8b50a2590d19e1d1e5f97d28f7104", + "uptime-dynamic-settings": "9de35c5aeaef915c5bc3c5b1632c33fb0f6f1c55", + "uptime-synthetics-api-key": "df9d8418ddc210d832a069a0fb796f73e63d1082", + "url": "d66c1f26ed23a392be3617a8444d713571f58380", + "usage-counters": "33e2081a52215293041da1100e6602fb553ff446", + "visualization": "f45d06858a5634c9ed0367e11eb44f7f7dde0be2", + "workplace_search_telemetry": "45bd03e12b060c08381b0fd325d939f80d08c914", + } + `); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts index 4fd5ca5cd2ae..eb9eb8a42069 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts @@ -58,6 +58,7 @@ const previouslyRegisteredTypes = [ 'fleet-agents', 'fleet-enrollment-api-keys', 'fleet-preconfiguration-deletion-record', + 'fleet-fleet-server-host', 'graph-workspace', 'guided-setup-state', 'guided-onboarding-guide-state', diff --git a/src/dev/build/tasks/download_cloud_dependencies.ts b/src/dev/build/tasks/download_cloud_dependencies.ts index 25730fc88bd0..266901afb67d 100644 --- a/src/dev/build/tasks/download_cloud_dependencies.ts +++ b/src/dev/build/tasks/download_cloud_dependencies.ts @@ -9,6 +9,7 @@ import Path from 'path'; import del from 'del'; import Axios from 'axios'; +import Fsp from 'fs/promises'; import { Task, downloadToDisk, downloadToString } from '../lib'; export const DownloadCloudDependencies: Task = { @@ -38,18 +39,41 @@ export const DownloadCloudDependencies: Task = { return Promise.all(downloads); }; + const writeManifest = async (manifestUrl: string, manifestJSON: object) => { + const destination = config.resolveFromRepo('.beats', 'beats_manifest.json'); + return Fsp.writeFile( + destination, + JSON.stringify( + { + manifest_url: manifestUrl, + ...manifestJSON, + }, + null, + 2 + ) + ); + }; + let buildId = ''; + let manifestUrl = ''; + let manifestJSON = null; const buildUrl = `https://${subdomain}.elastic.co/beats/latest/${config.getBuildVersion()}.json`; try { const latest = await Axios.get(buildUrl); buildId = latest.data.build_id; + manifestUrl = latest.data.manifest_url; + manifestJSON = (await Axios.get(manifestUrl)).data; + if (!(manifestUrl && manifestJSON)) throw new Error('Missing manifest.'); } catch (e) { log.error(`Unable to find Beats artifacts for ${config.getBuildVersion()} at ${buildUrl}.`); throw e; } + await del([config.resolveFromRepo('.beats')]); await downloadBeat('metricbeat', buildId); await downloadBeat('filebeat', buildId); + + await writeManifest(manifestUrl, manifestJSON); }, }; diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx index 5a03e2dd3187..6588cefdb8f2 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx @@ -211,7 +211,7 @@ describe('GaugeComponent', function () { }); describe('ticks and color bands', () => { - it('sets proper color bands for values smaller than maximum', () => { + it('sets proper color bands and ticks on color bands for values smaller than maximum', () => { const palette = { type: 'palette' as const, name: 'custom', @@ -236,6 +236,7 @@ describe('GaugeComponent', function () { }, } as GaugeRenderProps; const goal = shallowWithIntl().find(Goal); + expect(goal.prop('ticks')).toEqual([0, 1, 2, 3, 4, 10]); expect(goal.prop('bands')).toEqual([0, 1, 2, 3, 4, 10]); }); it('sets proper color bands if palette steps are smaller than minimum', () => { diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx index b75d613f814a..37e3ca6bdd8f 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx @@ -18,6 +18,8 @@ import { GaugeLabelMajorMode, GaugeLabelMajorModes, GaugeColorModes, + GaugeShapes, + GaugeTicksPositions, } from '../../common'; import { getAccessorsFromArgs, @@ -30,7 +32,7 @@ import { } from './utils'; import { getIcons } from './utils/icons'; import './index.scss'; -import { GaugeCentralMajorMode } from '../../common/types'; +import { GaugeCentralMajorMode, GaugeTicksPosition } from '../../common/types'; import { isBulletShape, isRoundShape } from '../../common/utils'; import './gauge.scss'; @@ -135,6 +137,35 @@ const getPreviousSectionValue = (value: number, bands: number[]) => { return prevSectionValue; }; +function getTicksLabels(baseStops: number[]) { + const tenPercentRange = (Math.max(...baseStops) - Math.min(...baseStops)) * 0.1; + const lastIndex = baseStops.length - 1; + return baseStops.filter((stop, i) => { + if (i === 0 || i === lastIndex) { + return true; + } + + return !( + stop - baseStops[i - 1] < tenPercentRange || baseStops[lastIndex] - stop < tenPercentRange + ); + }); +} + +function getTicks( + ticksPosition: GaugeTicksPosition, + range: [number, number], + colorBands?: number[], + percentageMode?: boolean +) { + if (ticksPosition === GaugeTicksPositions.HIDDEN) { + return []; + } + + if (ticksPosition === GaugeTicksPositions.BANDS && colorBands) { + return colorBands && getTicksLabels(colorBands); + } +} + export const GaugeComponent: FC = memo( ({ data, args, uiState, formatFactory, paletteService, chartsThemeService, renderComplete }) => { const { @@ -146,6 +177,7 @@ export const GaugeComponent: FC = memo( labelMajorMode, centralMajor, centralMajorMode, + ticksPosition, commonLabel, } = args; @@ -294,6 +326,12 @@ export const GaugeComponent: FC = memo( actualValue = actualValueToPercentsLegacy(palette?.params as CustomPaletteState, actualValue); } + const totalTicks = getTicks(ticksPosition, [min, max], bands, args.percentageMode); + const ticks = + totalTicks && gaugeType === GaugeShapes.CIRCLE + ? totalTicks.slice(0, totalTicks.length - 1) + : totalTicks; + const goalConfig = getGoalConfig(gaugeType); const labelMajorTitle = getTitle(labelMajorMode, labelMajor, metricColumn?.name); @@ -329,6 +367,7 @@ export const GaugeComponent: FC = memo( tickValueFormatter={({ value: tickValue }) => tickFormatter.convert(tickValue)} tooltipValueFormatter={(tooltipValue) => tickFormatter.convert(tooltipValue)} bands={bands} + ticks={ticks} domain={{ min, max }} bandFillColor={ colorMode === GaugeColorModes.PALETTE diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap index 9a7a7d5a5035..defaca87fad1 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap @@ -26,6 +26,7 @@ Object { "as": "legacyMetricVis", "type": "render", "value": Object { + "canNavigateToLens": false, "visConfig": Object { "dimensions": Object { "metrics": Array [ diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts index 8ec638d139bf..a7d655107fc2 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts @@ -200,6 +200,7 @@ export const metricVisFunction = (): MetricVisExpressionFunctionDefinition => ({ ...(args.bucket ? { bucket: args.bucket } : {}), }, }, + canNavigateToLens: Boolean(handlers?.variables?.canNavigateToLens), }, }; }, diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts index 0c7c0331975e..f0a63b012dc0 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts @@ -38,6 +38,7 @@ export interface MetricVisRenderConfig { visType: typeof visType; visData: Datatable; visConfig: Pick; + canNavigateToLens: boolean; } export type MetricVisExpressionFunctionDefinition = ExpressionFunctionDefinition< diff --git a/src/plugins/chart_expressions/expression_legacy_metric/public/__stories__/metric_renderer.stories.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/__stories__/metric_renderer.stories.tsx index 36fc6cd712f8..ba2835806f4f 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/public/__stories__/metric_renderer.stories.tsx +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/__stories__/metric_renderer.stories.tsx @@ -43,6 +43,7 @@ const style: MetricStyle = { }; const config: MetricVisRenderConfig = { + canNavigateToLens: false, visType, visData: { type: 'datatable', diff --git a/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/metric_component.test.tsx.snap b/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/metric_component.test.tsx.snap index 106d45bc4a87..36c92d316d83 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/metric_component.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/metric_component.test.tsx.snap @@ -28,7 +28,6 @@ Array [ } } onFilter={[Function]} - renderComplete={[MockFunction]} style={ Object { "bgColor": false, @@ -116,4 +115,4 @@ exports[`MetricVisComponent should render correct structure for single metric 1` } } /> -`; \ No newline at end of file +`; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.test.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.test.tsx index 314f83798625..d61e6d19a6ea 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.test.tsx +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; import { Datatable } from '@kbn/expressions-plugin/common'; import MetricVisComponent, { MetricVisComponentProps } from './metric_component'; import { LabelPosition } from '../../common/constants'; @@ -76,15 +76,15 @@ describe('MetricVisComponent', function () { ...propOverrides, }; - return shallow(); + return ; }; it('should render component', () => { - expect(getComponent().exists()).toBe(true); + expect(shallow(getComponent()).exists()).toBe(true); }); it('should render correct structure for single metric', function () { - expect(getComponent()).toMatchSnapshot(); + expect(shallow(getComponent())).toMatchSnapshot(); }); it('should render correct structure for multi-value metrics', function () { @@ -110,6 +110,36 @@ describe('MetricVisComponent', function () { }, }); - expect(component).toMatchSnapshot(); + expect(shallow(component)).toMatchSnapshot(); + }); + + it('should call renderComplete once for multi-value metrics', function () { + const renderComplete = jest.fn(); + const component = getComponent({ + renderComplete, + filterable: [true, false], + visData: { + type: 'datatable', + columns: [ + { id: 'col-0', name: '1st percentile of bytes', meta: { type: 'number' } }, + { id: 'col-1', name: '99th percentile of bytes', meta: { type: 'number' } }, + ], + rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }], + }, + visParams: { + ...visParams, + dimensions: { + ...visParams.dimensions, + metrics: [ + { accessor: 0, type: 'vis_dimension', format: { id: 'number', params: {} } }, + { accessor: 1, type: 'vis_dimension', format: { id: 'number', params: {} } }, + ], + }, + }, + }); + + mount(component); + + expect(renderComplete).toHaveBeenCalledTimes(1); }); }); diff --git a/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx index 8da0c4d4d8b8..2bebcda46bbe 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx @@ -127,7 +127,7 @@ class MetricVisComponent extends Component { return this.props.visParams.metric.autoScale && this.props.visParams.metric.colorFullBackground; }; - private renderMetric = (metric: MetricOptions, index: number) => { + private renderMetric = (metric: MetricOptions, index: number, arrayRef: MetricOptions[]) => { const hasBuckets = this.props.visParams.dimensions.bucket !== undefined; const MetricComponent = this.props.visParams.metric.autoScale ? AutoScaleMetricVisValue @@ -157,7 +157,7 @@ class MetricVisComponent extends Component { autoScale={this.props.visParams.metric.autoScale} colorFullBackground={this.props.visParams.metric.colorFullBackground} labelConfig={this.props.visParams.metric.labels} - renderComplete={this.props.renderComplete} + renderComplete={arrayRef.length - 1 === index ? this.props.renderComplete : undefined} /> ); }; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/public/expression_renderers/metric_vis_renderer.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/expression_renderers/metric_vis_renderer.tsx index 798bc62938ca..456a7f9cbf8a 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/public/expression_renderers/metric_vis_renderer.tsx +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/expression_renderers/metric_vis_renderer.tsx @@ -67,7 +67,7 @@ export const getMetricVisRenderer: ( name: EXPRESSION_METRIC_NAME, displayName: 'metric visualization', reuseDomNode: true, - render: async (domNode, { visData, visConfig }, handlers) => { + render: async (domNode, { visData, visConfig, canNavigateToLens }, handlers) => { const { core, plugins } = getStartDeps(); handlers.onDestroy(() => { @@ -82,9 +82,12 @@ export const getMetricVisRenderer: ( const visualizationType = extractVisualizationType(executionContext); if (containerType && visualizationType) { - plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, [ + const events = [ `render_${visualizationType}_legacy_metric`, - ]); + canNavigateToLens ? `render_${visualizationType}_legacy_metric_convertable` : undefined, + ].filter((event): event is string => Boolean(event)); + + plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, events); } handlers.done(); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts index 79427cbe4d3c..4664902d1387 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts @@ -24,7 +24,11 @@ describe('layeredXyVis', () => { expect(result).toEqual({ type: 'render', as: XY_VIS, - value: { args: { ...rest, layers: [sampleExtendedLayer] } }, + value: { + args: { ...rest, layers: [sampleExtendedLayer] }, + syncColors: false, + syncTooltips: false, + }, }); }); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts index 1eed6aca1cea..8dcc58cda01a 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts @@ -61,6 +61,8 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) (handlers.variables?.embeddableTitle as string) ?? handlers.getExecutionContext?.()?.description, }, + syncColors: handlers?.isSyncColorsEnabled?.() ?? false, + syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, }, }; }; diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts index 7e6afa0dd23a..67c7ab8d1e29 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts @@ -38,6 +38,8 @@ describe('xyVis', () => { }, ], }, + syncColors: false, + syncTooltips: false, }, }); }); @@ -344,6 +346,8 @@ describe('xyVis', () => { }, ], }, + syncColors: false, + syncTooltips: false, }, }); }); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts index defda933784d..e29f1e5ffff3 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts @@ -136,6 +136,8 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => { (handlers.variables?.embeddableTitle as string) ?? handlers.getExecutionContext?.()?.description, }, + syncColors: handlers?.isSyncColorsEnabled?.() ?? false, + syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, }, }; }; diff --git a/src/plugins/chart_expressions/expression_xy/common/index.ts b/src/plugins/chart_expressions/expression_xy/common/index.ts index b39002c8549c..9b404de95872 100755 --- a/src/plugins/chart_expressions/expression_xy/common/index.ts +++ b/src/plugins/chart_expressions/expression_xy/common/index.ts @@ -9,6 +9,8 @@ export const PLUGIN_ID = 'expressionXy'; export const PLUGIN_NAME = 'expressionXy'; +export { LayerTypes } from './constants'; + export type { XYArgs, EndValue, diff --git a/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts index 2a4baeb22313..de387b411337 100644 --- a/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts @@ -16,6 +16,8 @@ import { XYProps } from './expression_functions'; export interface XYChartProps { args: XYProps; + syncTooltips: boolean; + syncColors: boolean; } export interface XYRender { diff --git a/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.scss b/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.scss index 0fa98d5e5db8..1e705724fbed 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.scss +++ b/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.scss @@ -6,12 +6,14 @@ padding: $euiSizeS; table { + table-layout: fixed; + width: 100%; + td, th { text-align: left; padding: $euiSizeXS; overflow-wrap: break-word; - word-wrap: break-word; } } } @@ -22,10 +24,13 @@ } } +.detailedTooltip__labelContainer { + max-width: $euiSizeXL * 5; +} + .detailedTooltip__labelContainer, .detailedTooltip__valueContainer { overflow-wrap: break-word; - word-wrap: break-word; } .detailedTooltip__label { diff --git a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx index 893037a9f75f..5606bad9050c 100644 --- a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx @@ -226,8 +226,8 @@ export const getXyChartRenderer = ({ onClickValue={onClickValue} onSelectRange={onSelectRange} renderMode={handlers.getRenderMode()} - syncColors={handlers.isSyncColorsEnabled()} - syncTooltips={handlers.isSyncTooltipsEnabled()} + syncColors={config.syncColors} + syncTooltips={config.syncTooltips} uiState={handlers.uiState as PersistedState} renderComplete={renderComplete} /> diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/interval.test.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/interval.test.ts index d39c6bb4e63b..71d12db8ffb9 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/interval.test.ts +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/interval.test.ts @@ -18,7 +18,7 @@ describe('calculateMinInterval', () => { beforeEach(() => { const { layers, ...restArgs } = sampleArgs().args; - xyProps = { args: { ...restArgs, layers } }; + xyProps = { args: { ...restArgs, layers }, syncColors: false, syncTooltips: false }; layer = xyProps.args.layers[0] as DataLayerConfig; layer.xScaleType = 'time'; }); diff --git a/src/plugins/chart_expressions/expression_xy/public/index.ts b/src/plugins/chart_expressions/expression_xy/public/index.ts index d9447400aa26..7578c4ad89f9 100755 --- a/src/plugins/chart_expressions/expression_xy/public/index.ts +++ b/src/plugins/chart_expressions/expression_xy/public/index.ts @@ -14,4 +14,6 @@ export function plugin() { return new ExpressionXyPlugin(); } +export { LayerTypes } from '../common'; + export type { ExpressionXyPluginSetup, ExpressionXyPluginStart } from './types'; diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 036c77fc6257..d8c66155d8b3 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -153,13 +153,17 @@ export class DashboardContainer extends Container { + this.controlGroup.untilReady().then(() => { + if (!this.controlGroup || isErrorEmbeddable(this.controlGroup)) return; + syncDashboardControlGroup({ + dashboardContainer: this, + controlGroup: this.controlGroup, + }).then((result) => { if (!result) return; const { onDestroyControlGroup } = result; this.onDestroyControlGroup = onDestroyControlGroup; - } - ); + }); + }); } this.subscriptions.add( diff --git a/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts b/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts index 4f44d0cf250d..edca78a37313 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts @@ -10,7 +10,7 @@ import _ from 'lodash'; import { Subscription } from 'rxjs'; import deepEqual from 'fast-deep-equal'; import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query'; -import { debounceTime, distinctUntilChanged, distinctUntilKeyChanged } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, distinctUntilKeyChanged, skip } from 'rxjs/operators'; import { ControlGroupInput, @@ -140,11 +140,10 @@ export const syncDashboardControlGroup = async ({ .pipe( distinctUntilChanged(({ filters: filtersA }, { filters: filtersB }) => compareAllFilters(filtersA, filtersB) - ) + ), + skip(1) // skip first filter output because it will have been applied in initialize ) - .subscribe(() => { - dashboardContainer.updateInput({ lastReloadRequestTime: Date.now() }); - }) + .subscribe(() => dashboardContainer.updateInput({ lastReloadRequestTime: Date.now() })) ); subscriptions.add( diff --git a/src/plugins/data/common/search/aggs/agg_config.test.ts b/src/plugins/data/common/search/aggs/agg_config.test.ts index bf6dfbd51b26..6ea409e327d5 100644 --- a/src/plugins/data/common/search/aggs/agg_config.test.ts +++ b/src/plugins/data/common/search/aggs/agg_config.test.ts @@ -191,6 +191,85 @@ describe('AggConfig', () => { expect(dsl.aggs[medianConfig.id]).toHaveProperty('percentiles'); expect(dsl.aggs[medianConfig.id].percentiles).toBe(football); }); + + it('properly handles nested sibling pipeline aggregations', () => { + const customBucket = { + type: 'date_histogram', + params: { + field: '@timestamp', + interval: '1h', + }, + }; + const customMetric = { + type: 'avg_bucket', + params: { + customBucket: { + type: 'date_histogram', + params: { + field: '@timestamp', + interval: '30m', + }, + }, + customMetric: { + type: 'sum', + params: { + field: 'bytes', + }, + }, + }, + }; + const configStates = [ + { + type: 'avg_bucket', + params: { + customBucket, + customMetric, + }, + }, + ]; + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }, jest.fn()); + const dsl = ac.toDsl(); + + expect(dsl).toMatchInlineSnapshot(` + Object { + "1": Object { + "avg_bucket": Object { + "buckets_path": "1-bucket>1-metric", + }, + }, + "1-bucket": Object { + "aggs": Object { + "1-bucket": Object { + "aggs": Object { + "1-metric": Object { + "sum": Object { + "field": "bytes", + }, + }, + }, + "date_histogram": Object { + "field": "@timestamp", + "fixed_interval": "30m", + "min_doc_count": 1, + "time_zone": "dateFormat:tz", + }, + }, + "1-metric": Object { + "avg_bucket": Object { + "buckets_path": "1-bucket>1-metric", + }, + }, + }, + "date_histogram": Object { + "calendar_interval": "1h", + "field": "@timestamp", + "min_doc_count": 1, + "time_zone": "dateFormat:tz", + }, + }, + } + `); + }); }); describe('::ensureIds', () => { diff --git a/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts b/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts index 752bacd7554e..fb47e5336282 100644 --- a/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts +++ b/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts @@ -6,21 +6,25 @@ * Side Public License, v 1. */ +import { siblingPipelineType } from '../../../..'; import { IMetricAggConfig } from '../metric_agg_type'; import { METRIC_TYPES } from '../metric_agg_types'; export const siblingPipelineAggWriter = (agg: IMetricAggConfig, output: Record) => { - const customMetric = agg.getParam('customMetric'); - - if (!customMetric) return; - - const metricAgg = customMetric; + const metricAgg = agg.getParam('customMetric'); const bucketAgg = agg.getParam('customBucket'); + if (!metricAgg) return; // if a bucket is selected, we must add this agg as a sibling to it, and add a metric to that bucket (or select one of its) if (metricAgg.type.name !== METRIC_TYPES.COUNT) { bucketAgg.subAggs = (output.subAggs || []).concat(metricAgg); output.params.buckets_path = `${bucketAgg.id}>${metricAgg.id}`; + + // If the metric is another nested sibling pipeline agg, we need to include it as a sub-agg of this agg's bucket agg + if (metricAgg.type.subtype === siblingPipelineType) { + const subAgg = metricAgg.getParam('customBucket'); + if (subAgg) bucketAgg.subAggs.push(subAgg); + } } else { output.params.buckets_path = bucketAgg.id + '>_count'; } diff --git a/src/plugins/data/common/search/session/status.ts b/src/plugins/data/common/search/session/status.ts index 790adbe7ba00..24fac7b69c2a 100644 --- a/src/plugins/data/common/search/session/status.ts +++ b/src/plugins/data/common/search/session/status.ts @@ -13,3 +13,9 @@ export enum SearchSessionStatus { CANCELLED = 'cancelled', EXPIRED = 'expired', } + +export enum SearchStatus { + IN_PROGRESS = 'in_progress', + ERROR = 'error', + COMPLETE = 'complete', +} diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index cbe3de9be4c7..7824a1db6362 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -6,8 +6,9 @@ * Side Public License, v 1. */ +import type { SavedObjectsFindResponse } from '@kbn/core/server'; import { SerializableRecord } from '@kbn/utility-types'; -import { SearchSessionStatus } from './status'; +import type { SearchSessionStatus, SearchStatus } from './status'; export const SEARCH_SESSION_TYPE = 'search-session'; export interface SearchSessionSavedObjectAttributes { @@ -24,25 +25,12 @@ export interface SearchSessionSavedObjectAttributes { * Creation time of the session */ created: string; - /** - * Last touch time of the session - */ - touched: string; + /** * Expiration time of the session. Expiration itself is managed by Elasticsearch. */ expires: string; - /** - * Time of transition into completed state, - * - * Can be "null" in case already completed session - * transitioned into in-progress session - */ - completed?: string | null; - /** - * status - */ - status: SearchSessionStatus; + /** * locatorId (see share.url.locators service) */ @@ -62,10 +50,6 @@ export interface SearchSessionSavedObjectAttributes { */ idMapping: Record; - /** - * This value is true if the session was actively stored by the user. If it is false, the session may be purged by the system. - */ - persisted: boolean; /** * The realm type/name & username uniquely identifies the user who created this search session */ @@ -76,6 +60,11 @@ export interface SearchSessionSavedObjectAttributes { * Version information to display warnings when trying to restore a session from a different version */ version: string; + + /** + * `true` if session was cancelled + */ + isCanceled?: boolean; } export interface SearchSessionRequestInfo { @@ -87,20 +76,30 @@ export interface SearchSessionRequestInfo { * Search strategy used to submit the search request */ strategy: string; - /** - * status - */ - status: string; +} + +export interface SearchSessionRequestStatus { + status: SearchStatus; /** * An optional error. Set if status is set to error. */ error?: string; } -export interface SearchSessionFindOptions { - page?: number; - perPage?: number; - sortField?: string; - sortOrder?: string; - filter?: string; +/** + * On-the-fly calculated search session status + */ +export interface SearchSessionStatusResponse { + status: SearchSessionStatus; +} + +/** + * List of search session objects with on-the-fly calculated search session statuses + */ +export interface SearchSessionsFindResponse + extends SavedObjectsFindResponse { + /** + * Map containing calculated statuses of search sessions from the find response + */ + statuses: Record; } diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index 802af347b8e6..cedfa3ee0227 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -72,6 +72,11 @@ export interface IKibanaSearchResponse { */ isRestored?: boolean; + /** + * Indicates whether the search has been saved to a search-session object and long keepAlive was set + */ + isStored?: boolean; + /** * Optional warnings returned from Elasticsearch (for example, deprecation warnings) */ @@ -119,6 +124,11 @@ export interface ISearchOptions { */ isStored?: boolean; + /** + * Whether the search was successfully polled after session was saved. Search was added to a session saved object and keepAlive extended. + */ + isSearchStored?: boolean; + /** * Whether the session is restored (i.e. search requests should re-use the stored search IDs, * rather than starting from scratch) @@ -148,5 +158,11 @@ export interface ISearchOptions { */ export type ISearchOptionsSerializable = Pick< ISearchOptions, - 'strategy' | 'legacyHitsTotal' | 'sessionId' | 'isStored' | 'isRestore' | 'executionContext' + | 'strategy' + | 'legacyHitsTotal' + | 'sessionId' + | 'isStored' + | 'isSearchStored' + | 'isRestore' + | 'executionContext' >; diff --git a/src/plugins/data/config.ts b/src/plugins/data/config.ts index 0ec794d9bd63..d4331ecd760b 100644 --- a/src/plugins/data/config.ts +++ b/src/plugins/data/config.ts @@ -13,42 +13,13 @@ export const searchSessionsConfigSchema = schema.object({ * Turns the feature on \ off (incl. removing indicator and management screens) */ enabled: schema.boolean({ defaultValue: true }), - /** - * pageSize controls how many search session objects we load at once while monitoring - * session completion - */ - pageSize: schema.number({ defaultValue: 100 }), - /** - * trackingInterval controls how often we track persisted search session objects progress - */ - trackingInterval: schema.duration({ defaultValue: '10s' }), - - /** - * cleanupInterval controls how often we track non-persisted search session objects for cleanup - */ - cleanupInterval: schema.duration({ defaultValue: '60s' }), - - /** - * expireInterval controls how often we track persisted search session objects for expiration - */ - expireInterval: schema.duration({ defaultValue: '60m' }), - - /** - * monitoringTaskTimeout controls for how long task manager waits for search session monitoring task to complete before considering it timed out, - * If tasks timeouts it receives cancel signal and next task starts in "trackingInterval" time - */ - monitoringTaskTimeout: schema.duration({ defaultValue: '5m' }), /** - * notTouchedTimeout controls how long do we store unpersisted search session results, - * after the last search in the session has completed + * notTouchedTimeout controls how long user can save a session after all searches completed. + * The client continues to poll searches to keep the alive until this timeout hits */ notTouchedTimeout: schema.duration({ defaultValue: '5m' }), - /** - * notTouchedInProgressTimeout controls how long do allow a search session to run after - * a user has navigated away without persisting - */ - notTouchedInProgressTimeout: schema.duration({ defaultValue: '1m' }), + /** * maxUpdateRetries controls how many retries we perform while attempting to save a search session */ @@ -60,15 +31,15 @@ export const searchSessionsConfigSchema = schema.object({ defaultExpiration: schema.duration({ defaultValue: '7d' }), management: schema.object({ /** - * maxSessions controls how many saved search sessions we display per page on the management screen. + * maxSessions controls how many saved search sessions we load on the management screen. */ - maxSessions: schema.number({ defaultValue: 10000 }), + maxSessions: schema.number({ defaultValue: 100 }), /** - * refreshInterval controls how often we refresh the management screen. + * refreshInterval controls how often we refresh the management screen. 0s as duration means that auto-refresh is turned off. */ - refreshInterval: schema.duration({ defaultValue: '10s' }), + refreshInterval: schema.duration({ defaultValue: '0s' }), /** - * refreshTimeout controls how often we refresh the management screen. + * refreshTimeout controls the timeout for loading search sessions on mgmt screen */ refreshTimeout: schema.duration({ defaultValue: '1m' }), expiresSoonWarning: schema.duration({ defaultValue: '1d' }), diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts index 68f1ff1910c9..73c86d8845a1 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts @@ -548,6 +548,7 @@ describe('SearchInterceptor', () => { sessionId, isStored: true, isRestore: true, + isSearchStored: false, strategy: 'ese', }, }) @@ -732,17 +733,21 @@ describe('SearchInterceptor', () => { ); sessionService.getSessionId.mockImplementation(() => sessionId); - const untrack = jest.fn(); - sessionService.trackSearch.mockImplementation(() => untrack); + const trackSearchComplete = jest.fn(); + sessionService.trackSearch.mockImplementation(() => ({ + complete: trackSearchComplete, + error: () => {}, + beforePoll: () => [{ isSearchStored: false }, () => {}], + })); const response = searchInterceptor.search({}, { pollInterval: 0, sessionId }); response.subscribe({ next, error }); await timeTravel(10); expect(sessionService.trackSearch).toBeCalledTimes(1); - expect(untrack).not.toBeCalled(); + expect(trackSearchComplete).not.toBeCalled(); await timeTravel(300); expect(sessionService.trackSearch).toBeCalledTimes(1); - expect(untrack).toBeCalledTimes(1); + expect(trackSearchComplete).toBeCalledTimes(1); }); test('session service should be able to cancel search', async () => { @@ -752,9 +757,6 @@ describe('SearchInterceptor', () => { ); sessionService.getSessionId.mockImplementation(() => sessionId); - const untrack = jest.fn(); - sessionService.trackSearch.mockImplementation(() => untrack); - const response = searchInterceptor.search({}, { pollInterval: 0, sessionId }); response.subscribe({ next, error }); await timeTravel(10); @@ -778,9 +780,6 @@ describe('SearchInterceptor', () => { ); sessionService.getSessionId.mockImplementation(() => sessionId); - const untrack = jest.fn(); - sessionService.trackSearch.mockImplementation(() => untrack); - const response1 = searchInterceptor.search( {}, { pollInterval: 0, sessionId: 'something different' } @@ -798,9 +797,6 @@ describe('SearchInterceptor', () => { sessionService.getSessionId.mockImplementation(() => undefined); sessionService.isCurrentSession.mockImplementation((_sessionId) => false); - const untrack = jest.fn(); - sessionService.trackSearch.mockImplementation(() => untrack); - const response1 = searchInterceptor.search( {}, { pollInterval: 0, sessionId: 'something different' } @@ -911,15 +907,22 @@ describe('SearchInterceptor', () => { expect(fetchMock).toBeCalledTimes(2); }); - test('should track searches that come from cache', async () => { + test('should not track searches that come from cache', async () => { mockFetchImplementation(partialCompleteResponse); sessionService.isCurrentSession.mockImplementation( (_sessionId) => _sessionId === sessionId ); sessionService.getSessionId.mockImplementation(() => sessionId); - const untrack = jest.fn(); - sessionService.trackSearch.mockImplementation(() => untrack); + const completeSearch = jest.fn(); + + sessionService.trackSearch.mockImplementation((params) => ({ + complete: completeSearch, + error: jest.fn(), + beforePoll: jest.fn(() => { + return [{ isSearchStored: false }, () => {}]; + }), + })); const req = { params: { @@ -932,14 +935,15 @@ describe('SearchInterceptor', () => { response.subscribe({ next, error, complete }); response2.subscribe({ next, error, complete }); await timeTravel(10); + expect(fetchMock).toBeCalledTimes(1); - expect(sessionService.trackSearch).toBeCalledTimes(2); - expect(untrack).not.toBeCalled(); + expect(sessionService.trackSearch).toBeCalledTimes(1); + expect(completeSearch).not.toBeCalled(); await timeTravel(300); // Should be called only 2 times (once per partial response) expect(fetchMock).toBeCalledTimes(2); - expect(sessionService.trackSearch).toBeCalledTimes(2); - expect(untrack).toBeCalledTimes(2); + expect(sessionService.trackSearch).toBeCalledTimes(1); + expect(completeSearch).toBeCalledTimes(1); expect(next).toBeCalledTimes(4); expect(error).toBeCalledTimes(0); @@ -1125,8 +1129,15 @@ describe('SearchInterceptor', () => { ); sessionService.getSessionId.mockImplementation(() => sessionId); - const untrack = jest.fn(); - sessionService.trackSearch.mockImplementation(() => untrack); + const completeSearch = jest.fn(); + + sessionService.trackSearch.mockImplementation((params) => ({ + complete: completeSearch, + error: jest.fn(), + beforePoll: jest.fn(() => { + return [{ isSearchStored: false }, () => {}]; + }), + })); const req = { params: { @@ -1149,7 +1160,6 @@ describe('SearchInterceptor', () => { expect(error).toBeCalledTimes(0); expect(complete).toBeCalledTimes(0); expect(sessionService.trackSearch).toBeCalledTimes(1); - expect(untrack).not.toBeCalled(); const next2 = jest.fn(); const error2 = jest.fn(); @@ -1161,9 +1171,9 @@ describe('SearchInterceptor', () => { abortController.abort(); await timeTravel(300); - // Both searches should be tracked and untracked - expect(sessionService.trackSearch).toBeCalledTimes(2); - expect(untrack).toBeCalledTimes(2); + // Only first searches should be tracked and untracked + expect(sessionService.trackSearch).toBeCalledTimes(1); + expect(completeSearch).toBeCalledTimes(1); // First search should error expect(next).toBeCalledTimes(1); @@ -1186,8 +1196,15 @@ describe('SearchInterceptor', () => { ); sessionService.getSessionId.mockImplementation(() => sessionId); - const untrack = jest.fn(); - sessionService.trackSearch.mockImplementation(() => untrack); + const completeSearch = jest.fn(); + + sessionService.trackSearch.mockImplementation((params) => ({ + complete: completeSearch, + error: jest.fn(), + beforePoll: jest.fn(() => { + return [{ isSearchStored: false }, () => {}]; + }), + })); const req = { params: { @@ -1206,7 +1223,7 @@ describe('SearchInterceptor', () => { expect(error).toBeCalledTimes(0); expect(complete).toBeCalledTimes(0); expect(sessionService.trackSearch).toBeCalledTimes(1); - expect(untrack).not.toBeCalled(); + expect(completeSearch).not.toBeCalled(); const next2 = jest.fn(); const error2 = jest.fn(); @@ -1222,8 +1239,8 @@ describe('SearchInterceptor', () => { abortController.abort(); await timeTravel(300); - expect(sessionService.trackSearch).toBeCalledTimes(2); - expect(untrack).toBeCalledTimes(2); + expect(sessionService.trackSearch).toBeCalledTimes(1); + expect(completeSearch).toBeCalledTimes(1); expect(next).toBeCalledTimes(2); expect(error).toBeCalledTimes(0); @@ -1243,7 +1260,6 @@ describe('SearchInterceptor', () => { (_sessionId) => _sessionId === sessionId ); sessionService.getSessionId.mockImplementation(() => sessionId); - sessionService.trackSearch.mockImplementation(() => jest.fn()); const req = { params: { @@ -1282,8 +1298,15 @@ describe('SearchInterceptor', () => { ); sessionService.getSessionId.mockImplementation(() => sessionId); - const untrack = jest.fn(); - sessionService.trackSearch.mockImplementation(() => untrack); + const completeSearch = jest.fn(); + + sessionService.trackSearch.mockImplementation((params) => ({ + complete: completeSearch, + error: jest.fn(), + beforePoll: jest.fn(() => { + return [{ isSearchStored: false }, () => {}]; + }), + })); const req = { params: { diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts index ed21e4ef1d1e..baa5eb30bca4 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts @@ -7,7 +7,16 @@ */ import { memoize, once } from 'lodash'; -import { BehaviorSubject, EMPTY, from, fromEvent, of, Subscription, throwError } from 'rxjs'; +import { + BehaviorSubject, + EMPTY, + from, + fromEvent, + Observable, + of, + Subscription, + throwError, +} from 'rxjs'; import { catchError, filter, @@ -42,6 +51,7 @@ import { IAsyncSearchOptions, IKibanaSearchRequest, IKibanaSearchResponse, + isCompleteResponse, ISearchOptions, ISearchOptionsSerializable, pollSearch, @@ -146,18 +156,24 @@ export class SearchInterceptor { : TimeoutErrorMode.CONTACT; } - private createRequestHash$(request: IKibanaSearchRequest, options: IAsyncSearchOptions) { - const { sessionId, isRestore } = options; + private createRequestHash$( + request: IKibanaSearchRequest, + options: IAsyncSearchOptions + ): Observable { + const { sessionId } = options; // Preference is used to ensure all queries go to the same set of shards and it doesn't need to be hashed // https://www.elastic.co/guide/en/elasticsearch/reference/current/search-shard-routing.html#shard-and-node-preference const { preference, ...params } = request.params || {}; const hashOptions = { ...params, sessionId, - isRestore, }; - return from(sessionId ? createRequestHash(hashOptions) : of(undefined)); + if (!sessionId) return of(undefined); // don't use cache if doesn't belong to a session + const sessionOptions = this.deps.session.getSearchOptions(options.sessionId); + if (sessionOptions?.isRestore) return of(undefined); // don't use cache if restoring a session + + return from(createRequestHash(hashOptions)); } /* @@ -206,6 +222,8 @@ export class SearchInterceptor { serializableOptions.legacyHitsTotal = combined.legacyHitsTotal; if (combined.strategy !== undefined) serializableOptions.strategy = combined.strategy; if (combined.isStored !== undefined) serializableOptions.isStored = combined.isStored; + if (combined.isSearchStored !== undefined) + serializableOptions.isSearchStored = combined.isSearchStored; if (combined.executionContext !== undefined) { serializableOptions.executionContext = combined.executionContext; } @@ -222,16 +240,47 @@ export class SearchInterceptor { options: IAsyncSearchOptions, searchAbortController: SearchAbortController ) { - const search = () => - this.runSearch( - { id, ...request }, - { ...options, abortSignal: searchAbortController.getSignal() } - ); const { sessionId, strategy } = options; + const search = () => { + const [{ isSearchStored }, afterPoll] = searchTracker?.beforePoll() ?? [ + { isSearchStored: false }, + ({ isSearchStored: boolean }) => {}, + ]; + return this.runSearch( + { id, ...request }, + { + ...options, + ...this.deps.session.getSearchOptions(sessionId), + abortSignal: searchAbortController.getSignal(), + isSearchStored, + } + ) + .then((result) => { + afterPoll({ isSearchStored: result.isStored ?? false }); + return result; + }) + .catch((err) => { + afterPoll({ isSearchStored: false }); + throw err; + }); + }; + + const searchTracker = this.deps.session.isCurrentSession(sessionId) + ? this.deps.session.trackSearch({ + abort: () => searchAbortController.abort(), + poll: async () => { + if (id) { + await search(); + } + }, + }) + : undefined; + // track if this search's session will be send to background // if yes, then we don't need to cancel this search when it is aborted - let isSavedToBackground = false; + let isSavedToBackground = + this.deps.session.isCurrentSession(sessionId) && this.deps.session.isStored(); const savedToBackgroundSub = this.deps.session.isCurrentSession(sessionId) && this.deps.session.state$ @@ -256,8 +305,15 @@ export class SearchInterceptor { ...options, abortSignal: searchAbortController.getSignal(), }).pipe( - tap((response) => (id = response.id)), + tap((response) => { + id = response.id; + + if (isCompleteResponse(response)) { + searchTracker?.complete(); + } + }), catchError((e: Error) => { + searchTracker?.error(); cancel(); return throwError(e); }), @@ -378,9 +434,6 @@ export class SearchInterceptor { ); this.pendingCount$.next(this.pendingCount$.getValue() + 1); - const untrackSearch = this.deps.session.isCurrentSession(sessionId) - ? this.deps.session.trackSearch({ abort: () => searchAbortController.abort() }) - : undefined; // Abort the replay if the abortSignal is aborted. // The underlaying search will not abort unless searchAbortController fires. @@ -410,10 +463,6 @@ export class SearchInterceptor { }), finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); - if (untrackSearch && this.deps.session.isCurrentSession(sessionId)) { - // untrack if this search still belongs to current session - untrackSearch(); - } }) ); }) diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 3fba162d7173..c88201405d7f 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -23,7 +23,6 @@ import { Storage } from '@kbn/kibana-utils-plugin/public'; import { ManagementSetup } from '@kbn/management-plugin/public'; import { ScreenshotModePluginStart } from '@kbn/screenshot-mode-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import moment from 'moment'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; import { @@ -118,7 +117,8 @@ export class SearchService implements Plugin { this.initializerContext, getStartServices, this.sessionsClient, - nowProvider + nowProvider, + this.usageCollector ); /** * A global object that intercepts all searches and provides convenience methods for cancelling @@ -256,9 +256,6 @@ export class SearchService implements Plugin { application, basePath: http.basePath, storage: new Storage(window.localStorage), - disableSaveAfterSessionCompletesTimeout: moment - .duration(config.search.sessions.notTouchedTimeout) - .asMilliseconds(), usageCollector: this.usageCollector, tourDisabled: screenshotMode.isScreenshotMode(), }) diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index c6706ff8cf72..3c64267bd178 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { ISessionsClient } from './sessions_client'; import { ISessionService } from './session_service'; import { SearchSessionState } from './search_session_state'; @@ -34,9 +34,17 @@ export function getSessionServiceMock(): jest.Mocked { state$: new BehaviorSubject(SearchSessionState.None).asObservable(), sessionMeta$: new BehaviorSubject({ state: SearchSessionState.None, + isContinued: false, }).asObservable(), + disableSaveAfterSearchesExpire$: of(false), renameCurrentSession: jest.fn(), - trackSearch: jest.fn((searchDescriptor) => () => {}), + trackSearch: jest.fn((searchDescriptor) => ({ + complete: jest.fn(), + error: jest.fn(), + beforePoll: jest.fn(() => { + return [{ isSearchStored: false }, () => {}]; + }), + })), destroy: jest.fn(), cancel: jest.fn(), isStored: jest.fn(), diff --git a/src/plugins/data/public/search/session/search_session_state.test.ts b/src/plugins/data/public/search/session/search_session_state.test.ts index 1137ceddb0da..c85c635b7fc1 100644 --- a/src/plugins/data/public/search/session/search_session_state.test.ts +++ b/src/plugins/data/public/search/session/search_session_state.test.ts @@ -8,7 +8,6 @@ import { createSessionStateContainer, SearchSessionState } from './search_session_state'; import type { SearchSessionSavedObject } from './sessions_client'; -import { SearchSessionStatus } from '../../../common'; const mockSavedObject: SearchSessionSavedObject = { id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', @@ -19,11 +18,8 @@ const mockSavedObject: SearchSessionSavedObject = { locatorId: 'my_url_generator_id', idMapping: {}, sessionId: 'session_id', - touched: new Date().toISOString(), created: new Date().toISOString(), expires: new Date().toISOString(), - status: SearchSessionStatus.COMPLETE, - persisted: true, version: '8.0.0', }, references: [], @@ -46,7 +42,7 @@ describe('Session state container', () => { expect(state.get().appName).toBe(appName); }); - test('track', () => { + test('trackSearch', () => { expect(() => state.transitions.trackSearch({})).toThrowError(); state.transitions.start({ appName }); @@ -55,12 +51,12 @@ describe('Session state container', () => { expect(state.selectors.getState()).toBe(SearchSessionState.Loading); }); - test('untrack', () => { + test('removeSearch', () => { state.transitions.start({ appName }); const search = {}; state.transitions.trackSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.Loading); - state.transitions.unTrackSearch(search); + state.transitions.removeSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.Completed); }); @@ -95,7 +91,7 @@ describe('Session state container', () => { expect(state.selectors.getState()).toBe(SearchSessionState.Loading); state.transitions.store(mockSavedObject); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading); - state.transitions.unTrackSearch(search); + state.transitions.removeSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundCompleted); state.transitions.clear(); expect(state.selectors.getState()).toBe(SearchSessionState.None); @@ -124,7 +120,7 @@ describe('Session state container', () => { const search = {}; state.transitions.trackSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading); - state.transitions.unTrackSearch(search); + state.transitions.removeSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.Restored); expect(() => state.transitions.store(mockSavedObject)).toThrowError(); diff --git a/src/plugins/data/public/search/session/search_session_state.ts b/src/plugins/data/public/search/session/search_session_state.ts index 329d40da89b6..9b4c5d3098cc 100644 --- a/src/plugins/data/public/search/session/search_session_state.ts +++ b/src/plugins/data/public/search/session/search_session_state.ts @@ -57,13 +57,28 @@ export enum SearchSessionState { Canceled = 'canceled', } +/** + * State of the tracked search + */ +export enum TrackedSearchState { + InProgress = 'inProgress', + Completed = 'completed', + Errored = 'errored', +} + +export interface TrackedSearch { + state: TrackedSearchState; + searchDescriptor: SearchDescriptor; + searchMeta: SearchMeta; +} + /** * Internal state of SessionService * {@link SearchSessionState} is inferred from this state * * @private */ -export interface SessionStateInternal { +export interface SessionStateInternal { /** * Current session Id * Empty means there is no current active session. @@ -92,10 +107,9 @@ export interface SessionStateInternal { isRestore: boolean; /** - * Set of currently running searches - * within a session and any info associated with them + * Set of all searches within a session and any info associated with them */ - pendingSearches: SearchDescriptor[]; + trackedSearches: Array>; /** * There was at least a single search in this session @@ -107,6 +121,17 @@ export interface SessionStateInternal { */ isCanceled: boolean; + /** + * If session was continued from a different app, + * If session continued from a different app, then it is very likely that `trackedSearches` + * doesn't have all the search that were included into the session. + * Session that was continued can't be saved because we can't guarantee all the searches saved. + * This limitation should be fixed in https://github.com/elastic/kibana/issues/121543 + * + * @deprecated - https://github.com/elastic/kibana/issues/121543 + */ + isContinued: boolean; + /** * Start time of the current session (from browser perspective) */ @@ -124,27 +149,34 @@ export interface SessionStateInternal { } const createSessionDefaultState: < - SearchDescriptor = unknown ->() => SessionStateInternal = () => ({ + SearchDescriptor = unknown, + SearchMeta extends {} = {} +>() => SessionStateInternal = () => ({ sessionId: undefined, appName: undefined, isStored: false, isRestore: false, isCanceled: false, + isContinued: false, isStarted: false, - pendingSearches: [], + trackedSearches: [], }); export interface SessionPureTransitions< SearchDescriptor = unknown, - S = SessionStateInternal + SearchMeta extends {} = {}, + S = SessionStateInternal > { start: (state: S) => ({ appName }: { appName: string }) => S; restore: (state: S) => (sessionId: string) => S; clear: (state: S) => () => S; store: (state: S) => (searchSessionSavedObject: SearchSessionSavedObject) => S; - trackSearch: (state: S) => (search: SearchDescriptor) => S; - unTrackSearch: (state: S) => (search: SearchDescriptor) => S; + trackSearch: (state: S) => (search: SearchDescriptor, meta?: SearchMeta) => S; + removeSearch: (state: S) => (search: SearchDescriptor) => S; + completeSearch: (state: S) => (search: SearchDescriptor) => S; + errorSearch: (state: S) => (search: SearchDescriptor) => S; + updateSearchMeta: (state: S) => (search: SearchDescriptor, meta: Partial) => S; + cancel: (state: S) => () => S; setSearchSessionSavedObject: ( state: S @@ -177,21 +209,78 @@ export const sessionPureTransitions: SessionPureTransitions = { searchSessionSavedObject, }; }, - trackSearch: (state) => (search) => { - if (!state.sessionId) throw new Error("Can't track search. Missing sessionId"); + trackSearch: + (state) => + (search, meta = {}) => { + if (!state.sessionId) throw new Error("Can't track search. Missing sessionId"); + return { + ...state, + isStarted: true, + trackedSearches: state.trackedSearches.concat({ + state: TrackedSearchState.InProgress, + searchDescriptor: search, + searchMeta: meta, + }), + completedTime: undefined, + }; + }, + removeSearch: (state) => (search) => { + const trackedSearches = state.trackedSearches.filter((s) => s.searchDescriptor !== search); return { ...state, - isStarted: true, - pendingSearches: state.pendingSearches.concat(search), - completedTime: undefined, + trackedSearches, + completedTime: + trackedSearches.filter((s) => s.state !== TrackedSearchState.InProgress).length === 0 + ? new Date() + : state.completedTime, }; }, - unTrackSearch: (state) => (search) => { - const pendingSearches = state.pendingSearches.filter((s) => s !== search); + completeSearch: (state) => (search) => { return { ...state, - pendingSearches, - completedTime: pendingSearches.length === 0 ? new Date() : state.completedTime, + trackedSearches: state.trackedSearches.map((s) => { + if (s.searchDescriptor === search) { + return { + ...s, + state: TrackedSearchState.Completed, + }; + } + + return s; + }), + }; + }, + errorSearch: (state) => (search) => { + return { + ...state, + trackedSearches: state.trackedSearches.map((s) => { + if (s.searchDescriptor === search) { + return { + ...s, + state: TrackedSearchState.Errored, + }; + } + + return s; + }), + }; + }, + updateSearchMeta: (state) => (search, meta) => { + return { + ...state, + trackedSearches: state.trackedSearches.map((s) => { + if (s.searchDescriptor === search) { + return { + ...s, + searchMeta: { + ...s.searchMeta, + ...meta, + }, + }; + } + + return s; + }), }; }, cancel: (state) => () => { @@ -232,14 +321,23 @@ export interface SessionMeta { startTime?: Date; canceledTime?: Date; completedTime?: Date; + + /** + * @deprecated - see remarks in {@link SessionStateInternal} + */ + isContinued: boolean; } export interface SessionPureSelectors< SearchDescriptor = unknown, - S = SessionStateInternal + SearchMeta extends {} = {}, + S = SessionStateInternal > { getState: (state: S) => () => SearchSessionState; getMeta: (state: S) => () => SessionMeta; + getSearch: ( + state: S + ) => (search: SearchDescriptor) => TrackedSearch | null; } export const sessionPureSelectors: SessionPureSelectors = { @@ -247,17 +345,22 @@ export const sessionPureSelectors: SessionPureSelectors = { if (!state.sessionId) return SearchSessionState.None; if (!state.isStarted) return SearchSessionState.None; if (state.isCanceled) return SearchSessionState.Canceled; + + const pendingSearches = state.trackedSearches.filter( + (s) => s.state === TrackedSearchState.InProgress + ); + switch (true) { case state.isRestore: - return state.pendingSearches.length > 0 + return pendingSearches.length > 0 ? SearchSessionState.BackgroundLoading : SearchSessionState.Restored; case state.isStored: - return state.pendingSearches.length > 0 + return pendingSearches.length > 0 ? SearchSessionState.BackgroundLoading : SearchSessionState.BackgroundCompleted; default: - return state.pendingSearches.length > 0 + return pendingSearches.length > 0 ? SearchSessionState.Loading : SearchSessionState.Completed; } @@ -272,24 +375,31 @@ export const sessionPureSelectors: SessionPureSelectors = { startTime: state.searchSessionSavedObject?.attributes.created ? new Date(state.searchSessionSavedObject?.attributes.created) : state.startTime, - completedTime: state.searchSessionSavedObject?.attributes.completed - ? new Date(state.searchSessionSavedObject?.attributes.completed) - : state.completedTime, + completedTime: state.completedTime, canceledTime: state.canceledTime, + isContinued: state.isContinued, }); }, + getSearch(state) { + return (search) => { + return state.trackedSearches.find((s) => s.searchDescriptor === search) ?? null; + }; + }, }; -export type SessionStateContainer = StateContainer< - SessionStateInternal, - SessionPureTransitions, - SessionPureSelectors +export type SessionStateContainer< + SearchDescriptor = unknown, + SearchMeta extends {} = {} +> = StateContainer< + SessionStateInternal, + SessionPureTransitions, + SessionPureSelectors >; -export const createSessionStateContainer = ( +export const createSessionStateContainer = ( { freeze = true }: { freeze: boolean } = { freeze: true } ): { - stateContainer: SessionStateContainer; + stateContainer: SessionStateContainer; sessionState$: Observable; sessionMeta$: Observable; } => { @@ -298,7 +408,7 @@ export const createSessionStateContainer = ( sessionPureTransitions, sessionPureSelectors, freeze ? undefined : { freeze: (s) => s } - ) as SessionStateContainer; + ) as unknown as SessionStateContainer; const sessionMeta$: Observable = stateContainer.state$.pipe( map(() => stateContainer.selectors.getMeta()), diff --git a/src/plugins/data/public/search/session/session_helpers.test.ts b/src/plugins/data/public/search/session/session_helpers.test.ts index c06185d43840..bc092a4f6ac3 100644 --- a/src/plugins/data/public/search/session/session_helpers.test.ts +++ b/src/plugins/data/public/search/session/session_helpers.test.ts @@ -23,7 +23,13 @@ let nowProvider: jest.Mocked; let currentAppId$: BehaviorSubject; beforeEach(() => { - const initializerContext = coreMock.createPluginInitializerContext(); + const initializerContext = coreMock.createPluginInitializerContext({ + search: { + sessions: { + notTouchedTimeout: '5m', + }, + }, + }); const startService = coreMock.createSetup().getStartServices; nowProvider = createNowProviderMock(); currentAppId$ = new BehaviorSubject('app'); @@ -50,6 +56,7 @@ beforeEach(() => { ]), getSessionsClientMock(), nowProvider, + undefined, { freezeState: false } // needed to use mocks inside state container ); state$ = new BehaviorSubject(SearchSessionState.None); @@ -67,8 +74,13 @@ describe('waitUntilNextSessionCompletes$', () => { 'emits when next session starts', fakeSchedulers((advance) => { sessionService.start(); - let untrackSearch = sessionService.trackSearch({ abort: () => {} }); - untrackSearch(); + + let { complete: completeSearch } = sessionService.trackSearch({ + abort: () => {}, + poll: async () => {}, + }); + + completeSearch(); const next = jest.fn(); const complete = jest.fn(); @@ -78,8 +90,12 @@ describe('waitUntilNextSessionCompletes$', () => { sessionService.start(); expect(next).not.toBeCalled(); - untrackSearch = sessionService.trackSearch({ abort: () => {} }); - untrackSearch(); + completeSearch = sessionService.trackSearch({ + abort: () => {}, + poll: async () => {}, + }).complete; + + completeSearch(); expect(next).not.toBeCalled(); advance(500); diff --git a/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx index d791cf51ddbe..9f29e2866fb6 100644 --- a/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -40,7 +40,6 @@ const timeFilter = dataStart.query.timefilter.timefilter as jest.Mocked refreshInterval$.pipe(map(() => {}))); timeFilter.getRefreshInterval.mockImplementation(() => refreshInterval$.getValue()); -const disableSaveAfterSessionCompletesTimeout = 5 * 60 * 1000; const tourDisabled = false; function Container({ children }: { children?: ReactNode }) { @@ -64,7 +63,6 @@ test("shouldn't show indicator in case no active search session", async () => { sessionService, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -93,7 +91,6 @@ test("shouldn't show indicator in case app hasn't opt-in", async () => { sessionService, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -124,7 +121,6 @@ test('should show indicator in case there is an active search session', async () sessionService: { ...sessionService, state$ }, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -150,7 +146,6 @@ test('should be disabled in case uiConfig says so ', async () => { sessionService: { ...sessionService, state$ }, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -175,7 +170,6 @@ test('should be disabled in case not enough permissions', async () => { sessionService: { ...sessionService, state$, hasAccess: () => false }, application, storage, - disableSaveAfterSessionCompletesTimeout, basePath, tourDisabled, }); @@ -195,20 +189,15 @@ test('should be disabled in case not enough permissions', async () => { }); describe('Completed inactivity', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - afterEach(() => { - jest.useRealTimers(); - }); test('save should be disabled after completed and timeout', async () => { const state$ = new BehaviorSubject(SearchSessionState.Loading); + const disableSaveAfterSearchesExpire$ = new BehaviorSubject(false); + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ - sessionService: { ...sessionService, state$ }, + sessionService: { ...sessionService, state$, disableSaveAfterSearchesExpire$ }, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -227,30 +216,10 @@ describe('Completed inactivity', () => { expect(screen.getByRole('button', { name: 'Save session' })).not.toBeDisabled(); act(() => { - jest.advanceTimersByTime(5 * 60 * 1000); - }); - - expect(screen.getByRole('button', { name: 'Save session' })).not.toBeDisabled(); - - act(() => { - state$.next(SearchSessionState.Completed); - }); - - expect(screen.getByRole('button', { name: 'Save session' })).not.toBeDisabled(); - - act(() => { - jest.advanceTimersByTime(2.5 * 60 * 1000); - }); - - expect(screen.getByRole('button', { name: 'Save session' })).not.toBeDisabled(); - expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(0); - - act(() => { - jest.advanceTimersByTime(2.5 * 60 * 1000); + disableSaveAfterSearchesExpire$.next(true); }); expect(screen.getByRole('button', { name: 'Save session' })).toBeDisabled(); - expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(1); }); }); @@ -270,7 +239,6 @@ describe('tour steps', () => { sessionService: { ...sessionService, state$ }, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -312,7 +280,6 @@ describe('tour steps', () => { sessionService: { ...sessionService, state$ }, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -347,7 +314,6 @@ describe('tour steps', () => { sessionService: { ...sessionService, state$ }, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled: true, @@ -393,7 +359,6 @@ describe('tour steps', () => { sessionService: { ...sessionService, state$ }, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -421,7 +386,6 @@ describe('tour steps', () => { sessionService: { ...sessionService, state$ }, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, diff --git a/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx b/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx index 727a188fc2c0..9a23342d58c6 100644 --- a/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx +++ b/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx @@ -7,8 +7,8 @@ */ import React, { useCallback, useEffect, useState } from 'react'; -import { debounce, distinctUntilChanged, mapTo, switchMap, tap } from 'rxjs/operators'; -import { merge, of, timer } from 'rxjs'; +import { debounce } from 'rxjs/operators'; +import { timer } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; import { RedirectAppLinks } from '@kbn/kibana-react-plugin/public'; @@ -25,11 +25,6 @@ export interface SearchSessionIndicatorDeps { application: ApplicationStart; basePath: IBasePath; storage: IStorageWrapper; - /** - * Controls for how long we allow to save a session, - * after the last search in the session has completed - */ - disableSaveAfterSessionCompletesTimeout: number; tourDisabled: boolean; usageCollector?: SearchUsageCollector; } @@ -38,7 +33,6 @@ export const createConnectedSearchSessionIndicator = ({ sessionService, application, storage, - disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, tourDisabled, @@ -49,25 +43,14 @@ export const createConnectedSearchSessionIndicator = ({ debounce((_state) => timer(_state === SearchSessionState.None ? 50 : 300)) // switch to None faster to quickly remove indicator when navigating away ); - const disableSaveAfterSessionCompleteTimedOut$ = sessionService.state$.pipe( - switchMap((_state) => - _state === SearchSessionState.Completed - ? merge(of(false), timer(disableSaveAfterSessionCompletesTimeout).pipe(mapTo(true))) - : of(false) - ), - distinctUntilChanged(), - tap((value) => { - if (value) usageCollector?.trackSessionIndicatorSaveDisabled(); - }) - ); - return () => { const state = useObservable(debouncedSessionServiceState$, SearchSessionState.None); const isSaveDisabledByApp = sessionService.getSearchSessionIndicatorUiConfig().isDisabled(); - const disableSaveAfterSessionCompleteTimedOut = useObservable( - disableSaveAfterSessionCompleteTimedOut$, + const disableSaveAfterSearchesExpire = useObservable( + sessionService.disableSaveAfterSearchesExpire$, false ); + const [searchSessionIndicator, setSearchSessionIndicator] = useState(null); const searchSessionIndicatorRef = useCallback((ref: SearchSessionIndicatorRef) => { @@ -82,7 +65,7 @@ export const createConnectedSearchSessionIndicator = ({ let managementDisabled = false; let managementDisabledReasonText: string = ''; - if (disableSaveAfterSessionCompleteTimedOut) { + if (disableSaveAfterSearchesExpire) { saveDisabled = true; saveDisabledReasonText = i18n.translate( 'data.searchSessionIndicator.disabledDueToTimeoutMessage', @@ -160,7 +143,7 @@ export const createConnectedSearchSessionIndicator = ({ startTime, completedTime, canceledTime, - } = useObservable(sessionService.sessionMeta$, { state }); + } = useObservable(sessionService.sessionMeta$, { state, isContinued: false }); const saveSearchSessionNameFn = useCallback(async (newName: string) => { await sessionService.renameCurrentSession(newName); }, []); diff --git a/src/plugins/data/public/search/session/session_indicator/search_session_indicator/search_session_indicator.tsx b/src/plugins/data/public/search/session/session_indicator/search_session_indicator/search_session_indicator.tsx index 239a4ea4c2aa..465c447039e5 100644 --- a/src/plugins/data/public/search/session/session_indicator/search_session_indicator/search_session_indicator.tsx +++ b/src/plugins/data/public/search/session/session_indicator/search_session_indicator/search_session_indicator.tsx @@ -75,7 +75,7 @@ const ContinueInBackgroundButton = ({ diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index d377cca07a73..7517174698d6 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -6,18 +6,19 @@ * Side Public License, v 1. */ -import { SessionService, ISessionService } from './session_service'; +import { ISessionService, SessionService } from './session_service'; import { coreMock } from '@kbn/core/public/mocks'; -import { take, toArray } from 'rxjs/operators'; +import { first, take, toArray } from 'rxjs/operators'; import { getSessionsClientMock } from './mocks'; import { BehaviorSubject } from 'rxjs'; import { SearchSessionState } from './search_session_state'; import { createNowProviderMock } from '../../now_provider/mocks'; import { NowProviderInternalContract } from '../../now_provider'; import { SEARCH_SESSIONS_MANAGEMENT_ID } from './constants'; -import type { SearchSessionSavedObject, ISessionsClient } from './sessions_client'; -import { SearchSessionStatus } from '../../../common'; +import type { ISessionsClient, SearchSessionSavedObject } from './sessions_client'; import { CoreStart } from '@kbn/core/public'; +import { SearchUsageCollector } from '../..'; +import { createSearchUsageCollectorMock } from '../collectors/mocks'; const mockSavedObject: SearchSessionSavedObject = { id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', @@ -28,11 +29,8 @@ const mockSavedObject: SearchSessionSavedObject = { locatorId: 'my_locator_id', idMapping: {}, sessionId: 'session_id', - touched: new Date().toISOString(), created: new Date().toISOString(), expires: new Date().toISOString(), - status: SearchSessionStatus.COMPLETE, - persisted: true, version: '8.0.0', }, references: [], @@ -46,9 +44,16 @@ describe('Session service', () => { let currentAppId$: BehaviorSubject; let toastService: jest.Mocked; let sessionsClient: jest.Mocked; + let usageCollector: jest.Mocked; beforeEach(() => { - const initializerContext = coreMock.createPluginInitializerContext(); + const initializerContext = coreMock.createPluginInitializerContext({ + search: { + sessions: { + notTouchedTimeout: 5 * 60 * 1000, + }, + }, + }); const startService = coreMock.createSetup().getStartServices; const startServicesMock = coreMock.createStart(); toastService = startServicesMock.notifications.toasts; @@ -60,6 +65,7 @@ describe('Session service', () => { id, attributes: { ...mockSavedObject.attributes, sessionId: id }, })); + usageCollector = createSearchUsageCollectorMock(); sessionService = new SessionService( initializerContext, () => @@ -83,6 +89,7 @@ describe('Session service', () => { ]), sessionsClient, nowProvider, + usageCollector, { freezeState: false } // needed to use mocks inside state container ); state$ = new BehaviorSubject(SearchSessionState.None); @@ -132,34 +139,86 @@ describe('Session service', () => { }); it('Tracks searches for current session', () => { - expect(() => sessionService.trackSearch({ abort: () => {} })).toThrowError(); + expect(() => + sessionService.trackSearch({ abort: () => {}, poll: async () => {} }) + ).toThrowError(); expect(state$.getValue()).toBe(SearchSessionState.None); sessionService.start(); - const untrack1 = sessionService.trackSearch({ abort: () => {} }); + const complete1 = sessionService.trackSearch({ + abort: () => {}, + poll: async () => {}, + }).complete; expect(state$.getValue()).toBe(SearchSessionState.Loading); - const untrack2 = sessionService.trackSearch({ abort: () => {} }); + const complete2 = sessionService.trackSearch({ + abort: () => {}, + poll: async () => {}, + }).complete; + expect(state$.getValue()).toBe(SearchSessionState.Loading); - untrack1(); + complete1(); expect(state$.getValue()).toBe(SearchSessionState.Loading); - untrack2(); + complete2(); expect(state$.getValue()).toBe(SearchSessionState.Completed); }); it('Cancels all tracked searches within current session', async () => { const abort = jest.fn(); + const poll = jest.fn(); sessionService.start(); - sessionService.trackSearch({ abort }); - sessionService.trackSearch({ abort }); - sessionService.trackSearch({ abort }); - const untrack = sessionService.trackSearch({ abort }); + sessionService.trackSearch({ abort, poll }); + sessionService.trackSearch({ abort, poll }); + sessionService.trackSearch({ abort, poll }); + const complete = sessionService.trackSearch({ abort, poll }).complete; + complete(); - untrack(); await sessionService.cancel(); expect(abort).toBeCalledTimes(3); }); + + describe('Keeping searches alive', () => { + let dateNowSpy: jest.SpyInstance; + let now = Date.now(); + const advanceTimersBy = (by: number) => { + now = now + by; + jest.advanceTimersByTime(by); + }; + beforeEach(() => { + dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => now); + now = Date.now(); + jest.useFakeTimers(); + }); + afterEach(() => { + dateNowSpy.mockRestore(); + jest.useRealTimers(); + }); + + it('Polls all completed searches to keep them alive', async () => { + const abort = jest.fn(); + const poll = jest.fn(() => Promise.resolve()); + + sessionService.enableStorage({ + getName: async () => 'Name', + getLocatorData: async () => ({ + id: 'id', + initialState: {}, + restoreState: {}, + }), + }); + sessionService.start(); + + const searchTracker = sessionService.trackSearch({ abort, poll }); + searchTracker.complete(); + + expect(poll).toHaveBeenCalledTimes(0); + + advanceTimersBy(30000); + + expect(poll).toHaveBeenCalledTimes(1); + }); + }); }); it('Can continue previous session from another app', async () => { @@ -171,6 +230,7 @@ describe('Session service', () => { sessionService.continue(sessionId!); expect(sessionService.getSessionId()).toBe(sessionId); + expect((await sessionService.sessionMeta$.pipe(first()).toPromise())!.isContinued).toBe(true); }); it('Calling clear() more than once still allows previous session from another app to continue', async () => { @@ -213,7 +273,7 @@ describe('Session service', () => { it('Continue drops client side loading state', async () => { const sessionId = sessionService.start(); - sessionService.trackSearch({ abort: () => {} }); + sessionService.trackSearch({ abort: () => {}, poll: async () => {} }); expect(state$.getValue()).toBe(SearchSessionState.Loading); sessionService.clear(); // even allow to call clear multiple times @@ -389,4 +449,96 @@ describe('Session service', () => { }) ); }); + + describe('disableSaveAfterSearchesExpire$', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { + jest.useRealTimers(); + }); + + test('disables save after session completes on timeout', async () => { + const emitResult: boolean[] = []; + sessionService.disableSaveAfterSearchesExpire$.subscribe((result) => { + emitResult.push(result); + }); + + sessionService.start(); + const complete = sessionService.trackSearch({ + abort: () => {}, + poll: async () => {}, + }).complete; + + complete(); + + expect(emitResult).toEqual([false]); + + jest.advanceTimersByTime(2 * 60 * 1000); // 2 minutes + + expect(emitResult).toEqual([false]); + + jest.advanceTimersByTime(3 * 60 * 1000); // 3 minutes + + expect(emitResult).toEqual([false, true]); + + sessionService.start(); + + expect(emitResult).toEqual([false, true, false]); + }); + + test('disables save for continued from different app sessions', async () => { + const emitResult: boolean[] = []; + sessionService.disableSaveAfterSearchesExpire$.subscribe((result) => { + emitResult.push(result); + }); + + const sessionId = sessionService.start(); + + const complete = sessionService.trackSearch({ + abort: () => {}, + poll: async () => {}, + }).complete; + + complete(); + + expect(emitResult).toEqual([false]); + + sessionService.clear(); + + sessionService.continue(sessionId); + + expect(emitResult).toEqual([false, true]); + + sessionService.start(); + + expect(emitResult).toEqual([false, true, false]); + }); + + test('emits usage once', async () => { + const emitResult: boolean[] = []; + sessionService.disableSaveAfterSearchesExpire$.subscribe((result) => { + emitResult.push(result); + }); + sessionService.disableSaveAfterSearchesExpire$.subscribe(); // testing that source is shared + + sessionService.start(); + const complete = sessionService.trackSearch({ + abort: () => {}, + poll: async () => {}, + }).complete; + + expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(0); + + complete(); + + jest.advanceTimersByTime(5 * 60 * 1000); // 5 minutes + + expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(1); + + sessionService.start(); + + expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index 4b5d4da33132..e71b8ed32182 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -7,32 +7,116 @@ */ import { PublicContract, SerializableRecord } from '@kbn/utility-types'; -import { distinctUntilChanged, map, startWith } from 'rxjs/operators'; -import { Observable, Subscription } from 'rxjs'; +import { + distinctUntilChanged, + filter, + map, + mapTo, + mergeMap, + repeat, + startWith, + switchMap, + takeUntil, + tap, +} from 'rxjs/operators'; +import { + BehaviorSubject, + combineLatest, + EMPTY, + from, + merge, + Observable, + of, + Subscription, + timer, +} from 'rxjs'; import { PluginInitializerContext, StartServicesAccessor, ToastsStart as ToastService, } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { SearchUsageCollector } from '../..'; import { ConfigSchema } from '../../../config'; -import { createSessionStateContainer } from './search_session_state'; import type { - SearchSessionState, SessionMeta, SessionStateContainer, SessionStateInternal, } from './search_session_state'; +import { + createSessionStateContainer, + SearchSessionState, + TrackedSearchState, +} from './search_session_state'; import { ISessionsClient } from './sessions_client'; import { ISearchOptions } from '../../../common'; import { NowProviderInternalContract } from '../../now_provider'; import { SEARCH_SESSIONS_MANAGEMENT_ID } from './constants'; import { formatSessionName } from './lib/session_name_formatter'; +/** + * Polling interval for keeping completed searches alive + * until the user saves the session + */ +const KEEP_ALIVE_COMPLETED_SEARCHES_INTERVAL = 30000; + export type ISessionService = PublicContract; interface TrackSearchDescriptor { + /** + * Cancel the search + */ abort: () => void; + + /** + * Keep polling the search to keep it alive + */ + poll: () => Promise; + + /** + * Notify search that session is being saved, could be used to restart the search with different params + * @deprecated - this is used as an escape hatch for TSVB/Timelion to restart a search with different params + */ + onSavingSession?: ( + options: Required> + ) => Promise; +} + +interface TrackSearchMeta { + /** + * Time that indicates when last time this search was polled + */ + lastPollingTime: Date; + + /** + * If the keep_alive of this search was extended up to saved session keep_alive + */ + isStored: boolean; +} + +/** + * Api to manage tracked search + */ +interface TrackSearchHandler { + /** + * Transition search into "complete" status + */ + complete(): void; + + /** + * Transition search into "error" status + */ + error(): void; + + /** + * Call to notify when search is about to be polled to get current search state to build `searchOptions` from (mainly isSearchStored), + * When poll completes or errors, call `afterPoll` callback and confirm is search was successfully stored + */ + beforePoll(): [ + currentSearchState: { isSearchStored: boolean }, + afterPoll: (newSearchState: { isSearchStored: boolean }) => void + ]; } /** @@ -82,9 +166,26 @@ interface SearchSessionIndicatorUiConfig { */ export class SessionService { public readonly state$: Observable; - private readonly state: SessionStateContainer; + private readonly state: SessionStateContainer; public readonly sessionMeta$: Observable; + + /** + * Emits `true` when session completes and `config.search.sessions.notTouchedTimeout` duration has passed. + * Used to stop keeping searches alive after some times and disabled "save session" button + * + * or when failed to extend searches after session completes + */ + private readonly _disableSaveAfterSearchesExpire$ = new BehaviorSubject(false); + + /** + * Emits `true` when it is no longer possible to save a session: + * - Failed to keep searches alive after they completed + * - `config.search.sessions.notTouchedTimeout` after searches completed hit + * - Continued session from a different app and lost information about previous searches (https://github.com/elastic/kibana/issues/121543) + */ + public readonly disableSaveAfterSearchesExpire$: Observable; + private searchSessionInfoProvider?: SearchSessionInfoProvider; private searchSessionIndicatorUiConfig?: Partial; private subscription = new Subscription(); @@ -105,16 +206,50 @@ export class SessionService { getStartServices: StartServicesAccessor, private readonly sessionsClient: ISessionsClient, private readonly nowProvider: NowProviderInternalContract, + private readonly usageCollector?: SearchUsageCollector, { freezeState = true }: { freezeState: boolean } = { freezeState: true } ) { - const { stateContainer, sessionState$, sessionMeta$ } = - createSessionStateContainer({ - freeze: freezeState, - }); + const { stateContainer, sessionState$, sessionMeta$ } = createSessionStateContainer< + TrackSearchDescriptor, + TrackSearchMeta + >({ + freeze: freezeState, + }); this.state$ = sessionState$; this.state = stateContainer; this.sessionMeta$ = sessionMeta$; + this.disableSaveAfterSearchesExpire$ = combineLatest([ + this._disableSaveAfterSearchesExpire$, + this.sessionMeta$.pipe(map((meta) => meta.isContinued)), + ]).pipe( + map( + ([_disableSaveAfterSearchesExpire, isSessionContinued]) => + _disableSaveAfterSearchesExpire || isSessionContinued + ), + distinctUntilChanged() + ); + + const notTouchedTimeout = moment + .duration(initializerContext.config.get().search.sessions.notTouchedTimeout) + .asMilliseconds(); + + this.subscription.add( + this.state$ + .pipe( + switchMap((_state) => + _state === SearchSessionState.Completed + ? merge(of(false), timer(notTouchedTimeout).pipe(mapTo(true))) + : of(false) + ), + distinctUntilChanged(), + tap((value) => { + if (value) this.usageCollector?.trackSessionIndicatorSaveDisabled(); + }) + ) + .subscribe(this._disableSaveAfterSearchesExpire$) + ); + this.subscription.add( sessionMeta$ .pipe( @@ -155,6 +290,54 @@ export class SessionService { }) ); }); + + // keep completed searches alive until user explicitly saves the session + this.subscription.add( + this.getSession$() + .pipe( + switchMap((sessionId) => { + if (!sessionId) return EMPTY; + if (this.isStored()) return EMPTY; // no need to keep searches alive because session and searches are already stored + if (!this.hasAccess()) return EMPTY; // don't need to keep searches alive if the user can't save session + if (!this.isSessionStorageReady()) return EMPTY; // don't need to keep searches alive if app doesn't allow saving session + + const schedulePollSearches = () => { + return timer(KEEP_ALIVE_COMPLETED_SEARCHES_INTERVAL).pipe( + mergeMap(() => { + const searchesToKeepAlive = this.state.get().trackedSearches.filter( + (s) => + !s.searchMeta.isStored && + s.state === TrackedSearchState.Completed && + s.searchMeta.lastPollingTime.getTime() < Date.now() - 5000 // don't poll if was very recently polled + ); + + return from( + Promise.all( + searchesToKeepAlive.map((s) => + s.searchDescriptor.poll().catch((e) => { + // eslint-disable-next-line no-console + console.warn( + `Error while polling search to keep it alive. Considering that it is no longer possible to extend a session.`, + e + ); + if (this.isCurrentSession(sessionId)) { + this._disableSaveAfterSearchesExpire$.next(true); + } + }) + ) + ) + ); + }), + repeat(), + takeUntil(this.disableSaveAfterSearchesExpire$.pipe(filter((disable) => disable))) + ); + }; + + return schedulePollSearches(); + }) + ) + .subscribe(() => {}) + ); } /** @@ -167,15 +350,51 @@ export class SessionService { } /** - * Used to track pending searches within current session + * Used to track searches within current session * - * @param searchDescriptor - uniq object that will be used to untrack the search - * @returns untrack function + * @param searchDescriptor - uniq object that will be used to as search identifier + * @returns {@link TrackSearchHandler} */ - public trackSearch(searchDescriptor: TrackSearchDescriptor): () => void { - this.state.transitions.trackSearch(searchDescriptor); - return () => { - this.state.transitions.unTrackSearch(searchDescriptor); + public trackSearch(searchDescriptor: TrackSearchDescriptor): TrackSearchHandler { + this.state.transitions.trackSearch(searchDescriptor, { + lastPollingTime: new Date(), + isStored: false, + }); + + return { + complete: () => { + this.state.transitions.completeSearch(searchDescriptor); + + // when search completes and session has just been saved, + // trigger polling once again to save search into a session and extend its keep_alive + if (this.isStored()) { + const search = this.state.selectors.getSearch(searchDescriptor); + if (search && !search.searchMeta.isStored) { + search.searchDescriptor.poll().catch((e) => { + // eslint-disable-next-line no-console + console.warn(`Failed to extend search after it was completed`, e); + }); + } + } + }, + error: () => { + this.state.transitions.errorSearch(searchDescriptor); + }, + beforePoll: () => { + const search = this.state.selectors.getSearch(searchDescriptor); + this.state.transitions.updateSearchMeta(searchDescriptor, { + lastPollingTime: new Date(), + }); + + return [ + { isSearchStored: search?.searchMeta?.isStored ?? false }, + ({ isSearchStored }) => { + this.state.transitions.updateSearchMeta(searchDescriptor, { + isStored: isSearchStored, + }); + }, + ]; + }, }; } @@ -244,6 +463,12 @@ export class SessionService { * * This is different from {@link restore} as it reuses search session state and search results held in client memory instead of restoring search results from elasticsearch * @param sessionId + * + * TODO: remove this functionality in favor of separate architecture for client side search cache + * that won't interfere with saving search sessions + * https://github.com/elastic/kibana/issues/121543 + * + * @deprecated */ public continue(sessionId: string) { if (this.lastSessionSnapshot?.sessionId === sessionId) { @@ -254,7 +479,8 @@ export class SessionService { // also have to drop all pending searches which are used to derive client side state of search session indicator, // if we weren't dropping this searches, then we would get into "infinite loading" state when continuing a session that was cleared with pending searches // possible solution to this problem is to refactor session service to support multiple sessions - pendingSearches: [], + trackedSearches: [], + isContinued: true, }); this.lastSessionSnapshot = undefined; } else { @@ -293,10 +519,13 @@ export class SessionService { * Request a cancellation of on-going search requests within current session */ public async cancel(): Promise { - const isStoredSession = this.state.get().isStored; - this.state.get().pendingSearches.forEach((s) => { - s.abort(); - }); + const isStoredSession = this.isStored(); + this.state + .get() + .trackedSearches.filter((s) => s.state === TrackedSearchState.InProgress) + .forEach((s) => { + s.searchDescriptor.abort(); + }); this.state.transitions.cancel(); if (isStoredSession) { await this.sessionsClient.delete(this.state.get().sessionId!); @@ -335,8 +564,41 @@ export class SessionService { }); // if we are still interested in this result - if (this.getSessionId() === sessionId) { + if (this.isCurrentSession(sessionId)) { this.state.transitions.store(searchSessionSavedObject); + + // trigger new poll for all completed searches that are not stored to propogate them into newly creates search session saved object and extend their keepAlive + const completedSearches = this.state + .get() + .trackedSearches.filter( + (s) => s.state === TrackedSearchState.Completed && !s.searchMeta.isStored + ); + const pollCompletedSearchesPromise = Promise.all( + completedSearches.map((s) => + s.searchDescriptor.poll().catch((e) => { + // eslint-disable-next-line no-console + console.warn('Failed to extend search after session was saved', e); + }) + ) + ); + + // notify all the searches with onSavingSession that session has been saved and saved object has been created + // don't wait for the result + const searchesWithSavingHandler = this.state + .get() + .trackedSearches.filter((s) => s.searchDescriptor.onSavingSession); + searchesWithSavingHandler.forEach((s) => + s.searchDescriptor.onSavingSession!({ + sessionId, + isRestore: this.isRestore(), + isStored: this.isStored(), + }).catch((e) => { + // eslint-disable-next-line no-console + console.warn('Failed to execute "onSavingSession" handler after session was saved', e); + }) + ); + + await pollCompletedSearchesPromise; } } diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index 12fd424e57dd..80fe88c22a95 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -14,7 +14,10 @@ import type { SavedObjectsUpdateResponse, SavedObjectsFindOptions, } from '@kbn/core/server'; -import type { SearchSessionSavedObjectAttributes } from '../../../common'; +import type { + SearchSessionSavedObjectAttributes, + SearchSessionsFindResponse, +} from '../../../common'; export type SearchSessionSavedObject = SavedObject; export type ISessionsClient = PublicContract; export interface SessionsClientDeps { @@ -62,7 +65,7 @@ export class SessionsClient { }); } - public find(options: Omit): Promise { + public find(options: Omit): Promise { return this.http!.post(`/internal/session/_find`, { body: JSON.stringify(options), }); diff --git a/src/plugins/data/public/search/session/sessions_mgmt/components/table/app_filter.tsx b/src/plugins/data/public/search/session/sessions_mgmt/components/table/app_filter.tsx index 48cfda93e1f9..05070814d9a5 100644 --- a/src/plugins/data/public/search/session/sessions_mgmt/components/table/app_filter.tsx +++ b/src/plugins/data/public/search/session/sessions_mgmt/components/table/app_filter.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; +import { SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { capitalize } from 'lodash'; import { UISession } from '../../types'; @@ -18,12 +18,7 @@ export const getAppFilter: (tableData: UISession[]) => SearchFilterConfig = (tab }), field: 'appId', multiSelect: 'or', - options: tableData.reduce((options: FieldValueOptionType[], { appId }) => { - const existingOption = options.find((o) => o.value === appId); - if (!existingOption) { - return [...options, { value: appId, view: capitalize(appId) }]; - } - - return options; - }, []), + options: [...new Set(tableData.map((data) => data.appId ?? 'unknown'))] + .sort() + .map((appId) => ({ value: appId, view: capitalize(appId) })), }); diff --git a/src/plugins/data/public/search/session/sessions_mgmt/components/table/status_filter.tsx b/src/plugins/data/public/search/session/sessions_mgmt/components/table/status_filter.tsx index 25fc7dc092ab..bf34b72c7971 100644 --- a/src/plugins/data/public/search/session/sessions_mgmt/components/table/status_filter.tsx +++ b/src/plugins/data/public/search/session/sessions_mgmt/components/table/status_filter.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; +import { SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { TableText } from '..'; @@ -20,14 +20,7 @@ export const getStatusFilter: (tableData: UISession[]) => SearchFilterConfig = ( }), field: 'status', multiSelect: 'or', - options: tableData.reduce((options: FieldValueOptionType[], session) => { - const { status: statusType } = session; - const existingOption = options.find((o) => o.value === statusType); - if (!existingOption) { - const view = {getStatusText(session.status)}; - return [...options, { value: statusType, view }]; - } - - return options; - }, []), + options: [...new Set(tableData.map((data) => data.status ?? 'unknown'))] + .sort() + .map((status) => ({ value: status, view: {getStatusText(status)} })), }); diff --git a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx index 1fe4dbe0468e..ac554af701d0 100644 --- a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx +++ b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx @@ -68,13 +68,15 @@ describe('Background Search Session Management Table', () => { id: 'wtywp9u2802hahgp-flps', url: '/app/great-app-url/#48', appId: 'canvas', - status: SearchSessionStatus.IN_PROGRESS, created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', idMapping: {}, }, }, ], + statuses: { + 'wtywp9u2802hahgp-flps': { status: SearchSessionStatus.EXPIRED }, + }, }; }; diff --git a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.tsx b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.tsx index b887d9af43f5..833ad3ec7e75 100644 --- a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.tsx +++ b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.tsx @@ -12,7 +12,6 @@ import { CoreStart } from '@kbn/core/public'; import moment from 'moment'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; -import useInterval from 'react-use/lib/useInterval'; import { TableText } from '..'; import { SEARCH_SESSIONS_TABLE_ID } from '../../../../../../common'; import { SearchSessionsMgmtAPI } from '../../lib/api'; @@ -47,6 +46,7 @@ export function SearchSessionsMgmtTable({ const [debouncedIsLoading, setDebouncedIsLoading] = useState(false); const [pagination, setPagination] = useState({ pageIndex: 0 }); const showLatestResultsHandler = useRef(); + const refreshTimeoutRef = useRef(null); const refreshInterval = useMemo( () => moment.duration(config.management.refreshInterval).asMilliseconds(), [config.management.refreshInterval] @@ -63,30 +63,44 @@ export function SearchSessionsMgmtTable({ // refresh behavior const doRefresh = useCallback(async () => { + if (refreshTimeoutRef.current) { + clearTimeout(refreshTimeoutRef.current); + refreshTimeoutRef.current = null; + } + setIsLoading(true); const renderResults = (results: UISession[]) => { setTableData(results); }; showLatestResultsHandler.current = renderResults; - let results: UISession[] = []; - try { - results = await api.fetchTableData(); - } catch (e) {} // eslint-disable-line no-empty - if (showLatestResultsHandler.current === renderResults) { - renderResults(results); - setIsLoading(false); + if (document.visibilityState !== 'hidden') { + let results: UISession[] = []; + try { + results = await api.fetchTableData(); + } catch (e) {} // eslint-disable-line no-empty + + if (showLatestResultsHandler.current === renderResults) { + renderResults(results); + setIsLoading(false); + } } - }, [api]); + + if (showLatestResultsHandler.current === renderResults && refreshInterval > 0) { + if (refreshTimeoutRef.current) clearTimeout(refreshTimeoutRef.current); + refreshTimeoutRef.current = window.setTimeout(doRefresh, refreshInterval); + } + }, [api, refreshInterval]); // initial data load useEffect(() => { doRefresh(); searchUsageCollector.trackSessionsListLoaded(); + return () => { + if (refreshTimeoutRef.current) clearTimeout(refreshTimeoutRef.current); + }; }, [doRefresh, searchUsageCollector]); - useInterval(doRefresh, refreshInterval); - const onActionComplete: OnActionComplete = () => { doRefresh(); }; diff --git a/src/plugins/data/public/search/session/sessions_mgmt/lib/api.test.ts b/src/plugins/data/public/search/session/sessions_mgmt/lib/api.test.ts index 29a925d06690..73e1e8dc0866 100644 --- a/src/plugins/data/public/search/session/sessions_mgmt/lib/api.test.ts +++ b/src/plugins/data/public/search/session/sessions_mgmt/lib/api.test.ts @@ -52,14 +52,16 @@ describe('Search Sessions Management API', () => { attributes: { name: 'Veggie', appId: 'pizza', - status: 'complete', initialState: {}, restoreState: {}, idMapping: [], }, }, ], - } as SavedObjectsFindResponse; + statuses: { + 'hello-pizza-123': { status: 'complete' }, + }, + }; }); const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { @@ -93,7 +95,7 @@ describe('Search Sessions Management API', () => { `); }); - test('completed session with expired time is showed as expired', async () => { + test('expired session is showed as expired', async () => { sessionsClient.find = jest.fn().mockImplementation(async () => { return { saved_objects: [ @@ -102,7 +104,6 @@ describe('Search Sessions Management API', () => { attributes: { name: 'Veggie', appId: 'pizza', - status: 'complete', expires: moment().subtract(3, 'days'), initialState: {}, restoreState: {}, @@ -110,7 +111,10 @@ describe('Search Sessions Management API', () => { }, }, ], - } as SavedObjectsFindResponse; + statuses: { + 'hello-pizza-123': { status: 'expired' }, + }, + }; }); const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { diff --git a/src/plugins/data/public/search/session/sessions_mgmt/lib/api.ts b/src/plugins/data/public/search/session/sessions_mgmt/lib/api.ts index 9830cb436f7d..4fac2d36203c 100644 --- a/src/plugins/data/public/search/session/sessions_mgmt/lib/api.ts +++ b/src/plugins/data/public/search/session/sessions_mgmt/lib/api.ts @@ -21,7 +21,7 @@ import { } from '../types'; import { ISessionsClient } from '../../sessions_client'; import { SearchUsageCollector } from '../../../collectors'; -import { SearchSessionStatus } from '../../../../../common'; +import { SearchSessionsFindResponse, SearchSessionStatus } from '../../../../../common'; import { SearchSessionsConfigSchema } from '../../../../../config'; type LocatorsStart = SharePluginStart['url']['locators']; @@ -42,25 +42,6 @@ function getActions(status: UISearchSessionState) { return actions; } -/** - * Status we display on mgtm UI might be different from the one inside the saved object - * @param status - */ -function getUIStatus(session: PersistedSearchSessionSavedObjectAttributes): UISearchSessionState { - const isSessionExpired = () => { - const curTime = moment(); - return curTime.diff(moment(session.expires), 'ms') > 0; - }; - - switch (session.status) { - case SearchSessionStatus.COMPLETE: - case SearchSessionStatus.IN_PROGRESS: - return isSessionExpired() ? SearchSessionStatus.EXPIRED : session.status; - } - - return session.status; -} - function getUrlFromState(locators: LocatorsStart, locatorId: string, state: SerializableRecord) { try { const locator = locators.get(locatorId); @@ -75,7 +56,11 @@ function getUrlFromState(locators: LocatorsStart, locatorId: string, state: Seri // Helper: factory for a function to map server objects to UI objects const mapToUISession = - (locators: LocatorsStart, config: SearchSessionsConfigSchema) => + ( + locators: LocatorsStart, + config: SearchSessionsConfigSchema, + sessionStatuses: SearchSessionsFindResponse['statuses'] + ) => async ( savedObject: SavedObject ): Promise => { @@ -91,7 +76,7 @@ const mapToUISession = version, } = savedObject.attributes; - const status = getUIStatus(savedObject.attributes); + const status = sessionStatuses[savedObject.id]?.status; const actions = getActions(status); // TODO: initialState should be saved without the searchSessionID @@ -141,9 +126,7 @@ export class SearchSessionsMgmtAPI { page: 1, perPage: mgmtConfig.maxSessions, sortField: 'created', - sortOrder: 'asc', - searchFields: ['persisted'], - search: 'true', + sortOrder: 'desc', }) ); const timeout$ = timer(refreshTimeout.asMilliseconds()).pipe( @@ -165,7 +148,9 @@ export class SearchSessionsMgmtAPI { const savedObjects = result.saved_objects as Array< SavedObject >; - return await Promise.all(savedObjects.map(mapToUISession(this.deps.locators, this.config))); + return await Promise.all( + savedObjects.map(mapToUISession(this.deps.locators, this.config, result.statuses)) + ); } } catch (err) { // eslint-disable-next-line no-console diff --git a/src/plugins/data/server/config_deprecations.test.ts b/src/plugins/data/server/config_deprecations.test.ts index 9c54c793f661..674e1e9a6435 100644 --- a/src/plugins/data/server/config_deprecations.test.ts +++ b/src/plugins/data/server/config_deprecations.test.ts @@ -64,4 +64,87 @@ describe('Config Deprecations', () => { ] `); }); + + test('reports about old, no longer used configs', () => { + const config = { + data: { + search: { + sessions: { + enabled: false, + pageSize: 1000, + trackingInterval: '30s', + cleanupInterval: '30s', + expireInterval: '30s', + monitoringTaskTimeout: '30s', + notTouchedInProgressTimeout: '30s', + }, + }, + }, + }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); + expect(migrated).toMatchInlineSnapshot(` + Object { + "data": Object { + "search": Object { + "sessions": Object { + "enabled": false, + }, + }, + }, + } + `); + expect(messages).toMatchInlineSnapshot(` + Array [ + "You no longer need to configure \\"data.search.sessions.pageSize\\".", + "You no longer need to configure \\"data.search.sessions.trackingInterval\\".", + "You no longer need to configure \\"data.search.sessions.cleanupInterval\\".", + "You no longer need to configure \\"data.search.sessions.expireInterval\\".", + "You no longer need to configure \\"data.search.sessions.monitoringTaskTimeout\\".", + "You no longer need to configure \\"data.search.sessions.notTouchedInProgressTimeout\\".", + ] + `); + }); + + test('reports about old, no longer used configs from xpack.data_enhanced', () => { + const config = { + xpack: { + data_enhanced: { + search: { + sessions: { + enabled: false, + pageSize: 1000, + trackingInterval: '30s', + cleanupInterval: '30s', + expireInterval: '30s', + monitoringTaskTimeout: '30s', + notTouchedInProgressTimeout: '30s', + }, + }, + }, + }, + }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); + expect(migrated).toMatchInlineSnapshot(` + Object { + "data": Object { + "search": Object { + "sessions": Object { + "enabled": false, + }, + }, + }, + } + `); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting \\"xpack.data_enhanced.search.sessions\\" has been replaced by \\"data.search.sessions\\"", + "You no longer need to configure \\"data.search.sessions.pageSize\\".", + "You no longer need to configure \\"data.search.sessions.trackingInterval\\".", + "You no longer need to configure \\"data.search.sessions.cleanupInterval\\".", + "You no longer need to configure \\"data.search.sessions.expireInterval\\".", + "You no longer need to configure \\"data.search.sessions.monitoringTaskTimeout\\".", + "You no longer need to configure \\"data.search.sessions.notTouchedInProgressTimeout\\".", + ] + `); + }); }); diff --git a/src/plugins/data/server/config_deprecations.ts b/src/plugins/data/server/config_deprecations.ts index dcc168f306e1..3adbcf1c2178 100644 --- a/src/plugins/data/server/config_deprecations.ts +++ b/src/plugins/data/server/config_deprecations.ts @@ -8,8 +8,17 @@ import type { ConfigDeprecationProvider } from '@kbn/core/server'; -export const configDeprecationProvider: ConfigDeprecationProvider = ({ renameFromRoot }) => [ +export const configDeprecationProvider: ConfigDeprecationProvider = ({ + renameFromRoot, + unusedFromRoot, +}) => [ renameFromRoot('xpack.data_enhanced.search.sessions', 'data.search.sessions', { level: 'warning', }), + unusedFromRoot('data.search.sessions.pageSize', { level: 'warning' }), + unusedFromRoot('data.search.sessions.trackingInterval', { level: 'warning' }), + unusedFromRoot('data.search.sessions.cleanupInterval', { level: 'warning' }), + unusedFromRoot('data.search.sessions.expireInterval', { level: 'warning' }), + unusedFromRoot('data.search.sessions.monitoringTaskTimeout', { level: 'warning' }), + unusedFromRoot('data.search.sessions.notTouchedInProgressTimeout', { level: 'warning' }), ]; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 31944922c7b4..8630f2be1585 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -42,5 +42,6 @@ export function createSearchRequestHandlerContext() { extendSession: jest.fn(), cancelSession: jest.fn(), deleteSession: jest.fn(), + getSessionStatus: jest.fn(), }; } diff --git a/src/plugins/data/server/search/routes/session.test.ts b/src/plugins/data/server/search/routes/session.test.ts index dbc12e44b7a2..2fdf02c86ce6 100644 --- a/src/plugins/data/server/search/routes/session.test.ts +++ b/src/plugins/data/server/search/routes/session.test.ts @@ -65,6 +65,21 @@ describe('registerSessionRoutes', () => { expect(mockContext.search!.getSession).toHaveBeenCalledWith(id); }); + it('status calls getSessionStatus with sessionId', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const params = { id }; + + const mockRequest = httpServerMock.createKibanaRequest({ params }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[], [, statusHandler]] = mockRouter.get.mock.calls; + + await statusHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.getSessionStatus).toHaveBeenCalledWith(id); + }); + it('find calls findSession with options', async () => { const page = 1; const perPage = 5; diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts index 6106a69df207..c654fcb53adb 100644 --- a/src/plugins/data/server/search/routes/session.ts +++ b/src/plugins/data/server/search/routes/session.ts @@ -86,6 +86,35 @@ export function registerSessionRoutes(router: DataPluginRouter, logger: Logger): } ); + router.get( + { + path: '/internal/session/{id}/status', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + options: { + tags: [STORE_SEARCH_SESSIONS_ROLE_TAG], + }, + }, + async (context, request, res) => { + const { id } = request.params; + try { + const searchContext = await context.search; + const response = await searchContext!.getSessionStatus(id); + + return res.ok({ + body: response, + }); + } catch (e) { + const err = e.output?.payload || e; + logger.error(err); + return reportServerError(res, err); + } + } + ); + router.post( { path: '/internal/session/_find', diff --git a/src/plugins/data/server/search/saved_objects/search_session.ts b/src/plugins/data/server/search/saved_objects/search_session.ts index c77c8379af36..3eb1c6190dd6 100644 --- a/src/plugins/data/server/search/saved_objects/search_session.ts +++ b/src/plugins/data/server/search/saved_objects/search_session.ts @@ -16,9 +16,6 @@ export const searchSessionSavedObjectType: SavedObjectsType = { hidden: true, mappings: { properties: { - persisted: { - type: 'boolean', - }, sessionId: { type: 'keyword', }, @@ -31,15 +28,6 @@ export const searchSessionSavedObjectType: SavedObjectsType = { expires: { type: 'date', }, - touched: { - type: 'date', - }, - completed: { - type: 'date', - }, - status: { - type: 'keyword', - }, appId: { type: 'keyword', }, @@ -70,6 +58,9 @@ export const searchSessionSavedObjectType: SavedObjectsType = { version: { type: 'keyword', }, + isCanceled: { + type: 'boolean', + }, }, }, migrations: searchSessionSavedObjectMigrations, diff --git a/src/plugins/data/server/search/saved_objects/search_session_migration.test.ts b/src/plugins/data/server/search/saved_objects/search_session_migration.test.ts index 251d57e884ac..f18ac70bc4f4 100644 --- a/src/plugins/data/server/search/saved_objects/search_session_migration.test.ts +++ b/src/plugins/data/server/search/saved_objects/search_session_migration.test.ts @@ -11,9 +11,10 @@ import { SearchSessionSavedObjectAttributesPre$7$13$0, SearchSessionSavedObjectAttributesPre$7$14$0, SearchSessionSavedObjectAttributesPre$8$0$0, + SearchSessionSavedObjectAttributesPre$8$6$0, } from './search_session_migration'; import { SavedObject } from '@kbn/core/types'; -import { SEARCH_SESSION_TYPE, SearchSessionStatus } from '../../../common'; +import { SEARCH_SESSION_TYPE, SearchSessionStatus, SearchStatus } from '../../../common'; import { SavedObjectMigrationContext } from '@kbn/core/server'; describe('7.12.0 -> 7.13.0', () => { @@ -356,3 +357,98 @@ describe('7.14.0 -> 8.0.0', () => { ); }); }); + +describe('8.0.0 -> 8.6.0', () => { + const migration = searchSessionSavedObjectMigrations['8.6.0']; + + const mockSessionSavedObject: SavedObject = { + id: 'id', + type: SEARCH_SESSION_TYPE, + attributes: { + appId: 'my_app_id', + completed: '2021-03-29T00:00:00.000Z', + created: '2021-03-26T00:00:00.000Z', + expires: '2021-03-30T00:00:00.000Z', + idMapping: { + search1: { id: 'id1', strategy: 'ese', status: SearchStatus.COMPLETE }, + search2: { + id: 'id2', + strategy: 'sql', + status: SearchStatus.ERROR, + error: 'error', + }, + search3: { id: 'id3', strategy: 'es', status: SearchStatus.COMPLETE }, + }, + initialState: {}, + locatorId: undefined, + name: 'my_name', + persisted: true, + realmName: 'realmName', + realmType: 'realmType', + restoreState: {}, + sessionId: 'sessionId', + status: SearchSessionStatus.COMPLETE, + touched: '2021-03-29T00:00:00.000Z', + username: 'username', + version: '7.14.0', + }, + references: [], + }; + + test('migrates object', () => { + const migratedSession = migration(mockSessionSavedObject, {} as SavedObjectMigrationContext); + + expect(migratedSession.attributes).not.toHaveProperty('status'); + expect(migratedSession.attributes).not.toHaveProperty('touched'); + expect(migratedSession.attributes).not.toHaveProperty('completed'); + expect(migratedSession.attributes).not.toHaveProperty('persisted'); + expect(migratedSession.attributes.idMapping.search1).not.toHaveProperty('status'); + expect(migratedSession.attributes.idMapping.search2).not.toHaveProperty('error'); + + expect(migratedSession.attributes).toMatchInlineSnapshot(` + Object { + "appId": "my_app_id", + "created": "2021-03-26T00:00:00.000Z", + "expires": "2021-03-30T00:00:00.000Z", + "idMapping": Object { + "search1": Object { + "id": "id1", + "strategy": "ese", + }, + "search2": Object { + "id": "id2", + "strategy": "sql", + }, + "search3": Object { + "id": "id3", + "strategy": "es", + }, + }, + "initialState": Object {}, + "locatorId": undefined, + "name": "my_name", + "realmName": "realmName", + "realmType": "realmType", + "restoreState": Object {}, + "sessionId": "sessionId", + "username": "username", + "version": "7.14.0", + } + `); + }); + + test('status:canceled -> isCanceled', () => { + const migratedSession = migration( + { + ...mockSessionSavedObject, + attributes: { + ...mockSessionSavedObject.attributes, + status: SearchSessionStatus.CANCELLED, + }, + }, + {} as SavedObjectMigrationContext + ); + + expect(migratedSession.attributes.isCanceled).toBe(true); + }); +}); diff --git a/src/plugins/data/server/search/saved_objects/search_session_migration.ts b/src/plugins/data/server/search/saved_objects/search_session_migration.ts index 314db0fb8b58..fec4e3c33a93 100644 --- a/src/plugins/data/server/search/saved_objects/search_session_migration.ts +++ b/src/plugins/data/server/search/saved_objects/search_session_migration.ts @@ -39,12 +39,39 @@ export type SearchSessionSavedObjectAttributesPre$7$14$0 = Omit< * from using `urlGeneratorId` to `locatorId`. */ export type SearchSessionSavedObjectAttributesPre$8$0$0 = Omit< - SearchSessionSavedObjectAttributesLatest, + SearchSessionSavedObjectAttributesPre$8$6$0, 'locatorId' > & { urlGeneratorId?: string; }; +/** + * In 8.6.0 with search session refactoring and moving away from using task manager we are no longer track of: + * - `completed` - when session was completed + * - `persisted` - if session was saved + * - `touched` - when session was last updated (touched by the user) + * - `status` - status is no longer persisted. Except 'canceled' which was moved to `isCanceled` + * - `status` and `error` in idMapping (search info) + */ +export type SearchSessionSavedObjectAttributesPre$8$6$0 = Omit< + SearchSessionSavedObjectAttributesLatest, + 'idMapping' | 'isCanceled' +> & { + completed?: string | null; + persisted: boolean; + touched: string; + status: SearchSessionStatus; + idMapping: Record< + string, + { + id: string; + strategy: string; + status: string; + error?: string; + } + >; +}; + function getLocatorId(urlGeneratorId?: string) { if (!urlGeneratorId) return; if (urlGeneratorId === 'DISCOVER_APP_URL_GENERATOR') return 'DISCOVER_APP_LOCATOR'; @@ -89,4 +116,27 @@ export const searchSessionSavedObjectMigrations: SavedObjectMigrationMap = { const attributes = { ...otherAttrs, locatorId }; return { ...doc, attributes }; }, + '8.6.0': ( + doc: SavedObjectUnsanitizedDoc + ): SavedObjectUnsanitizedDoc => { + const { + attributes: { touched, completed, persisted, idMapping, status, ...otherAttrs }, + } = doc; + + const attributes: SearchSessionSavedObjectAttributesLatest = { + ...otherAttrs, + idMapping: Object.entries(idMapping).reduce< + SearchSessionSavedObjectAttributesLatest['idMapping'] + >((res, [searchHash, { status: searchStatus, error, ...otherSearchAttrs }]) => { + res[searchHash] = otherSearchAttrs; + return res; + }, {}), + }; + + if (status === SearchSessionStatus.CANCELLED) { + attributes.isCanceled = true; + } + + return { ...doc, attributes }; + }, }; diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index 56733b6871ec..cb3575caec51 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -33,6 +33,8 @@ import { ENHANCED_ES_SEARCH_STRATEGY } from '../../common'; let mockSessionClient: jest.Mocked; jest.mock('./session', () => { class SearchSessionService { + setup() {} + start() {} asScopedProvider = () => (request: any) => mockSessionClient; } return { @@ -193,7 +195,7 @@ describe('Search service', () => { it('does not fail if `trackId` throws', async () => { const searchRequest = { params: {} }; - const options = { sessionId, isStored: false, isRestore: false }; + const options = { sessionId, isStored: true, isRestore: false }; mockSessionClient.trackId = jest.fn().mockRejectedValue(undefined); mockStrategy.search.mockReturnValue( @@ -203,14 +205,32 @@ describe('Search service', () => { }) ); - await mockScopedClient.search(searchRequest, options).toPromise(); + const result = await mockScopedClient.search(searchRequest, options).toPromise(); expect(mockSessionClient.trackId).toBeCalledTimes(1); + expect(result?.isStored).toBeUndefined(); }); - it('calls `trackId` for every response, if the response contains an `id` and not restoring', async () => { + it("doesn't call trackId if session is not stored", async () => { const searchRequest = { params: {} }; - const options = { sessionId, isStored: false, isRestore: false }; + const options = { sessionId }; + mockSessionClient.trackId = jest.fn(); + + mockStrategy.search.mockReturnValue( + of({ + id: 'my_id', + rawResponse: {} as any, + }) + ); + + await mockScopedClient.search(searchRequest, options).toPromise(); + + expect(mockSessionClient.trackId).toBeCalledTimes(0); + }); + + it('calls `trackId` once, if the response contains an `id`, session is stored and not restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: true, isRestore: false }; mockSessionClient.trackId = jest.fn().mockResolvedValue(undefined); mockStrategy.search.mockReturnValue( @@ -228,10 +248,20 @@ describe('Search service', () => { await mockScopedClient.search(searchRequest, options).toPromise(); - expect(mockSessionClient.trackId).toBeCalledTimes(2); + expect(mockSessionClient.trackId).toBeCalledTimes(1); expect(mockSessionClient.trackId.mock.calls[0]).toEqual([searchRequest, 'my_id', options]); - expect(mockSessionClient.trackId.mock.calls[1]).toEqual([searchRequest, 'my_id', options]); + }); + + it('does not call `trackId` if search is already tracked', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: true, isRestore: false, isSearchStored: true }; + mockSessionClient.getId = jest.fn().mockResolvedValueOnce('my_id'); + mockSessionClient.trackId = jest.fn().mockResolvedValue(undefined); + + await mockScopedClient.search(searchRequest, options).toPromise(); + + expect(mockSessionClient.trackId).not.toBeCalled(); }); it('does not call `trackId` if restoring', async () => { diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index b5d140c36a0f..7523da61752b 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { firstValueFrom, from, Observable, throwError } from 'rxjs'; +import { concatMap, firstValueFrom, from, Observable, of, throwError } from 'rxjs'; import { pick } from 'lodash'; import moment from 'moment'; import { @@ -19,7 +19,7 @@ import { SharedGlobalConfig, StartServicesAccessor, } from '@kbn/core/server'; -import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; +import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { BfetchServerSetup } from '@kbn/bfetch-plugin/server'; import { ExpressionsServerSetup } from '@kbn/expressions-plugin/server'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; @@ -156,13 +156,7 @@ export class SearchService implements Plugin { registerSearchRoute(router); registerSessionRoutes(router, this.logger); - if (taskManager) { - this.sessionService.setup(core, { taskManager, security }); - } else { - // this should never happen in real world, but - // taskManager and security are optional deps because they are in x-pack - this.logger.debug('Skipping sessionService setup because taskManager is not available'); - } + this.sessionService.setup(core, { security }); core.http.registerRouteHandlerContext( 'search', @@ -273,9 +267,7 @@ export class SearchService implements Plugin { ): ISearchStart { const { elasticsearch, savedObjects, uiSettings } = core; - if (taskManager) { - this.sessionService.start(core, { taskManager }); - } + this.sessionService.start(core, {}); const aggs = this.aggsService.start({ fieldFormats, @@ -384,22 +376,55 @@ export class SearchService implements Plugin { }; const searchRequest$ = from(getSearchRequest()); + let isInternalSearchStored = false; // used to prevent tracking current search more than once const search$ = searchRequest$.pipe( - switchMap((searchRequest) => strategy.search(searchRequest, options, deps)), - withLatestFrom(searchRequest$), - tap(([response, requestWithId]) => { - if (!options.sessionId || !response.id || (options.isRestore && requestWithId.id)) return; - // intentionally swallow tracking error, as it shouldn't fail the search - deps.searchSessionsClient.trackId(request, response.id, options).catch((trackErr) => { - this.logger.error(trackErr); - }); - }), - map(([response, requestWithId]) => { - return { - ...response, - isRestored: !!requestWithId.id, - }; - }) + switchMap((searchRequest) => + strategy.search(searchRequest, options, deps).pipe( + concatMap((response) => { + response = { + ...response, + isRestored: !!searchRequest.id, + }; + + if ( + options.sessionId && // if within search session + options.isStored && // and search session was saved (saved object exists) + response.id && // and async search has started + !(options.isRestore && searchRequest.id) // and not restoring already tracked search + ) { + // then track this search inside the search-session saved object + + // check if search was already tracked and extended, don't track again in this case + if (options.isSearchStored || isInternalSearchStored) { + return of({ + ...response, + isStored: true, + }); + } else { + return from( + deps.searchSessionsClient.trackId(request, response.id, options) + ).pipe( + tap(() => { + isInternalSearchStored = true; + }), + map(() => ({ + ...response, + isStored: true, + })), + catchError((e) => { + this.logger.error( + `Error while trying to track search id: ${e?.message}. This might lead to untracked long-running search.` + ); + return of(response); + }) + ); + } + } else { + return of(response); + } + }) + ) + ) ); return search$; @@ -521,6 +546,7 @@ export class SearchService implements Plugin { extendSession: this.extendSession.bind(this, deps), cancelSession: this.cancelSession.bind(this, deps), deleteSession: this.deleteSession.bind(this, deps), + getSessionStatus: searchSessionsClient.status, }; }; }; diff --git a/src/plugins/data/server/search/session/check_non_persisted_sessions.test.ts b/src/plugins/data/server/search/session/check_non_persisted_sessions.test.ts deleted file mode 100644 index edbef1938a82..000000000000 --- a/src/plugins/data/server/search/session/check_non_persisted_sessions.test.ts +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { checkNonPersistedSessions as checkNonPersistedSessions$ } from './check_non_persisted_sessions'; -import { - SearchSessionStatus, - SearchSessionSavedObjectAttributes, - ENHANCED_ES_SEARCH_STRATEGY, - EQL_SEARCH_STRATEGY, -} from '../../../common'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { CheckSearchSessionsDeps, SearchStatus } from './types'; -import moment from 'moment'; -import { - SavedObjectsBulkUpdateObject, - SavedObjectsDeleteOptions, - SavedObjectsClientContract, -} from '@kbn/core/server'; -import { SearchSessionsConfigSchema } from '../../../config'; - -jest.useFakeTimers(); - -const checkNonPersistedSessions = ( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema -) => checkNonPersistedSessions$(deps, config).toPromise(); - -describe('checkNonPersistedSessions', () => { - let mockClient: any; - let savedObjectsClient: jest.Mocked; - const config: SearchSessionsConfigSchema = { - enabled: true, - pageSize: 5, - notTouchedInProgressTimeout: moment.duration(1, 'm'), - notTouchedTimeout: moment.duration(5, 'm'), - maxUpdateRetries: 3, - defaultExpiration: moment.duration(7, 'd'), - trackingInterval: moment.duration(10, 's'), - expireInterval: moment.duration(10, 'm'), - monitoringTaskTimeout: moment.duration(5, 'm'), - cleanupInterval: moment.duration(10, 's'), - management: {} as any, - }; - const mockLogger: any = { - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); - mockClient = { - asyncSearch: { - status: jest.fn(), - delete: jest.fn(), - }, - eql: { - status: jest.fn(), - delete: jest.fn(), - }, - }; - }); - - test('does nothing if there are no open sessions', async () => { - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [], - total: 0, - } as any); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).not.toBeCalled(); - }); - - describe('delete', () => { - test('doesnt delete a non persisted, recently touched session', async () => { - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ - { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - expires: moment().add(moment.duration(3, 'm')), - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(10, 's')), - idMapping: {}, - }, - }, - ], - total: 1, - } as any); - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).not.toBeCalled(); - }); - - test('doesnt delete a non persisted, completed session, within on screen time frame', async () => { - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ - { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.COMPLETE, - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(1, 'm')), - expires: moment().add(moment.duration(3, 'm')), - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.COMPLETE, - }, - }, - }, - }, - ], - total: 1, - } as any); - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).not.toBeCalled(); - }); - - test('deletes in space', async () => { - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ - { - id: '123', - namespaces: ['awesome'], - attributes: { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - expires: moment().add(moment.duration(3, 'm')), - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(2, 'm')), - idMapping: { - 'map-key': { - strategy: ENHANCED_ES_SEARCH_STRATEGY, - id: 'async-id', - }, - }, - }, - }, - ], - total: 1, - } as any); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.delete).toBeCalled(); - - const [, id, opts] = savedObjectsClient.delete.mock.calls[0]; - expect(id).toBe('123'); - expect((opts as SavedObjectsDeleteOptions).namespace).toBe('awesome'); - }); - - test('deletes a non persisted, abandoned session', async () => { - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ - { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(2, 'm')), - expires: moment().add(moment.duration(3, 'm')), - idMapping: { - 'map-key': { - strategy: ENHANCED_ES_SEARCH_STRATEGY, - id: 'async-id', - }, - }, - }, - }, - ], - total: 1, - } as any); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).toBeCalled(); - - expect(mockClient.asyncSearch.delete).toBeCalled(); - - const { id } = mockClient.asyncSearch.delete.mock.calls[0][0]; - expect(id).toBe('async-id'); - }); - - test('deletes a completed, not persisted session', async () => { - mockClient.asyncSearch.delete = jest.fn().mockResolvedValue(true); - - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ - { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.COMPLETE, - expires: moment().add(moment.duration(3, 'm')), - created: moment().subtract(moment.duration(30, 'm')), - touched: moment().subtract(moment.duration(6, 'm')), - idMapping: { - 'map-key': { - strategy: ENHANCED_ES_SEARCH_STRATEGY, - id: 'async-id', - status: SearchStatus.COMPLETE, - }, - 'eql-map-key': { - strategy: EQL_SEARCH_STRATEGY, - id: 'eql-async-id', - status: SearchStatus.COMPLETE, - }, - }, - }, - }, - ], - total: 1, - } as any); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).toBeCalled(); - - expect(mockClient.asyncSearch.delete).toBeCalled(); - expect(mockClient.eql.delete).not.toBeCalled(); - - const { id } = mockClient.asyncSearch.delete.mock.calls[0][0]; - expect(id).toBe('async-id'); - }); - - test('ignores errors thrown while deleting async searches', async () => { - mockClient.asyncSearch.delete = jest.fn().mockRejectedValueOnce(false); - - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ - { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.COMPLETE, - expires: moment().add(moment.duration(3, 'm')), - created: moment().subtract(moment.duration(30, 'm')), - touched: moment().subtract(moment.duration(6, 'm')), - idMapping: { - 'map-key': { - strategy: ENHANCED_ES_SEARCH_STRATEGY, - id: 'async-id', - status: SearchStatus.COMPLETE, - }, - }, - }, - }, - ], - total: 1, - } as any); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).toBeCalled(); - - expect(mockClient.asyncSearch.delete).toBeCalled(); - - const { id } = mockClient.asyncSearch.delete.mock.calls[0][0]; - expect(id).toBe('async-id'); - }); - - test("doesn't attempt to delete errored out async search", async () => { - mockClient.asyncSearch.delete = jest.fn(); - - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ - { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.ERROR, - expires: moment().add(moment.duration(3, 'm')), - created: moment().subtract(moment.duration(30, 'm')), - touched: moment().subtract(moment.duration(6, 'm')), - idMapping: { - 'map-key': { - strategy: ENHANCED_ES_SEARCH_STRATEGY, - id: 'async-id', - status: SearchStatus.ERROR, - }, - }, - }, - }, - ], - total: 1, - } as any); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).toBeCalled(); - expect(mockClient.asyncSearch.delete).not.toBeCalled(); - }); - }); - - describe('update', () => { - test('does nothing if the search is still running', async () => { - const so = { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(10, 's')), - expires: moment().add(moment.duration(3, 'm')), - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); - - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: true, - is_running: true, - }, - }); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).not.toBeCalled(); - }); - - test("doesn't re-check completed or errored searches", async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - savedObjectsClient.delete = jest.fn(); - const so = { - id: '123', - attributes: { - status: SearchSessionStatus.ERROR, - expires: moment().add(moment.duration(3, 'm')), - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.COMPLETE, - }, - 'another-search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.ERROR, - }, - }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(mockClient.asyncSearch.status).not.toBeCalled(); - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).not.toBeCalled(); - }); - - test('updates in space', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - namespaces: ['awesome'], - attributes: { - status: SearchSessionStatus.IN_PROGRESS, - expires: moment().add(moment.duration(3, 'm')), - touched: '123', - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); - - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: false, - is_running: false, - completion_status: 200, - }, - }); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(mockClient.asyncSearch.status).toBeCalledWith({ id: 'search-id' }, { meta: true }); - const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - const updatedAttributes = updateInput[0] as SavedObjectsBulkUpdateObject; - expect(updatedAttributes.namespace).toBe('awesome'); - }); - - test('updates to complete if the search is done', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - attributes: { - status: SearchSessionStatus.IN_PROGRESS, - expires: moment().add(moment.duration(3, 'm')), - touched: '123', - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); - - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: false, - is_running: false, - completion_status: 200, - }, - }); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - expect(mockClient.asyncSearch.status).toBeCalledWith({ id: 'search-id' }, { meta: true }); - const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; - expect(updatedAttributes.status).toBe(SearchSessionStatus.COMPLETE); - expect(updatedAttributes.touched).not.toBe('123'); - expect(updatedAttributes.completed).not.toBeUndefined(); - expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.COMPLETE); - expect(updatedAttributes.idMapping['search-hash'].error).toBeUndefined(); - - expect(savedObjectsClient.delete).not.toBeCalled(); - }); - - test('updates to error if the search is errored', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - attributes: { - expires: moment().add(moment.duration(3, 'm')), - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); - - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: false, - is_running: false, - completion_status: 500, - }, - }); - - await checkNonPersistedSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; - expect(updatedAttributes.status).toBe(SearchSessionStatus.ERROR); - expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.ERROR); - expect(updatedAttributes.idMapping['search-hash'].error).toBe( - 'Search completed with a 500 status' - ); - }); - }); -}); diff --git a/src/plugins/data/server/search/session/check_non_persisted_sessions.ts b/src/plugins/data/server/search/session/check_non_persisted_sessions.ts deleted file mode 100644 index f6314611afdb..000000000000 --- a/src/plugins/data/server/search/session/check_non_persisted_sessions.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SavedObjectsFindResult } from '@kbn/core/server'; -import moment from 'moment'; -import { EMPTY } from 'rxjs'; -import { catchError, concatMap } from 'rxjs/operators'; -import { nodeBuilder, KueryNode } from '@kbn/es-query'; -import { - ENHANCED_ES_SEARCH_STRATEGY, - SEARCH_SESSION_TYPE, - SearchSessionSavedObjectAttributes, - SearchSessionStatus, -} from '../../../common'; -import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page'; -import { CheckSearchSessionsDeps, SearchStatus } from './types'; -import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status'; -import { SearchSessionsConfigSchema } from '../../../config'; - -export const SEARCH_SESSIONS_CLEANUP_TASK_TYPE = 'search_sessions_cleanup'; -export const SEARCH_SESSIONS_CLEANUP_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_CLEANUP_TASK_TYPE}`; - -function isSessionStale( - session: SavedObjectsFindResult, - config: SearchSessionsConfigSchema -) { - const curTime = moment(); - // Delete cancelled sessions immediately - if (session.attributes.status === SearchSessionStatus.CANCELLED) return true; - // Delete if a running session wasn't polled for in the last notTouchedInProgressTimeout OR - // if a completed \ errored \ canceled session wasn't saved for within notTouchedTimeout - return ( - (session.attributes.status === SearchSessionStatus.IN_PROGRESS && - curTime.diff(moment(session.attributes.touched), 'ms') > - config.notTouchedInProgressTimeout.asMilliseconds()) || - (session.attributes.status !== SearchSessionStatus.IN_PROGRESS && - curTime.diff(moment(session.attributes.touched), 'ms') > - config.notTouchedTimeout.asMilliseconds()) - ); -} - -function checkNonPersistedSessionsPage( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema, - filter: KueryNode, - page: number -) { - const { logger, client, savedObjectsClient } = deps; - logger.debug(`${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Fetching sessions from page ${page}`); - return getSearchSessionsPage$(deps, filter, config.pageSize, page).pipe( - concatMap(async (nonPersistedSearchSessions) => { - if (!nonPersistedSearchSessions.total) return nonPersistedSearchSessions; - - logger.debug( - `${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Found ${nonPersistedSearchSessions.total} sessions, processing ${nonPersistedSearchSessions.saved_objects.length}` - ); - - const updatedSessions = await getAllSessionsStatusUpdates( - deps, - config, - nonPersistedSearchSessions - ); - const deletedSessionIds: string[] = []; - - await Promise.all( - nonPersistedSearchSessions.saved_objects.map(async (session) => { - if (isSessionStale(session, config)) { - // delete saved object to free up memory - // TODO: there's a potential rare edge case of deleting an object and then receiving a new trackId for that same session! - // Maybe we want to change state to deleted and cleanup later? - logger.debug(`Deleting stale session | ${session.id}`); - try { - deletedSessionIds.push(session.id); - await savedObjectsClient.delete(SEARCH_SESSION_TYPE, session.id, { - namespace: session.namespaces?.[0], - }); - } catch (e) { - logger.error( - `${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Error while deleting session ${session.id}: ${e.message}` - ); - } - - // Send a delete request for each async search to ES - Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { - const searchInfo = session.attributes.idMapping[searchKey]; - if (searchInfo.status === SearchStatus.ERROR) return; // skip attempting to delete async search in case we know it has errored out - - if (searchInfo.strategy === ENHANCED_ES_SEARCH_STRATEGY) { - try { - await client.asyncSearch.delete({ id: searchInfo.id }); - } catch (e) { - if (e.message !== 'resource_not_found_exception') { - logger.error( - `${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Error while deleting async_search ${searchInfo.id}: ${e.message}` - ); - } - } - } - }); - } - }) - ); - - const nonDeletedSessions = updatedSessions.filter((updateSession) => { - return deletedSessionIds.indexOf(updateSession.id) === -1; - }); - - await bulkUpdateSessions(deps, nonDeletedSessions); - - return nonPersistedSearchSessions; - }) - ); -} - -export function checkNonPersistedSessions( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema -) { - const { logger } = deps; - - const filters = nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'false'); - - return checkSearchSessionsByPage(checkNonPersistedSessionsPage, deps, config, filters).pipe( - catchError((e) => { - logger.error( - `${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Error while processing sessions: ${e?.message}` - ); - return EMPTY; - }) - ); -} diff --git a/src/plugins/data/server/search/session/check_persisted_sessions.test.ts b/src/plugins/data/server/search/session/check_persisted_sessions.test.ts deleted file mode 100644 index 1e6de567a0d7..000000000000 --- a/src/plugins/data/server/search/session/check_persisted_sessions.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { checkPersistedSessionsProgress } from './check_persisted_sessions'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import moment from 'moment'; -import { SavedObjectsClientContract } from '@kbn/core/server'; -import { SearchSessionsConfigSchema } from '../../../config'; - -describe('checkPersistedSessionsProgress', () => { - let mockClient: any; - let savedObjectsClient: jest.Mocked; - const config: SearchSessionsConfigSchema = { - enabled: true, - pageSize: 5, - notTouchedInProgressTimeout: moment.duration(1, 'm'), - notTouchedTimeout: moment.duration(5, 'm'), - maxUpdateRetries: 3, - defaultExpiration: moment.duration(7, 'd'), - trackingInterval: moment.duration(10, 's'), - cleanupInterval: moment.duration(10, 's'), - expireInterval: moment.duration(10, 'm'), - monitoringTaskTimeout: moment.duration(5, 'm'), - management: {} as any, - }; - const mockLogger: any = { - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); - mockClient = { - asyncSearch: { - status: jest.fn(), - delete: jest.fn(), - }, - eql: { - status: jest.fn(), - delete: jest.fn(), - }, - }; - }); - - test('fetches only running persisted sessions', async () => { - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [], - total: 0, - } as any); - - await checkPersistedSessionsProgress( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config - ); - - const [findInput] = savedObjectsClient.find.mock.calls[0]; - - expect(findInput.filter.arguments[0].arguments[0].value).toBe( - 'search-session.attributes.persisted' - ); - expect(findInput.filter.arguments[0].arguments[1].value).toBe('true'); - expect(findInput.filter.arguments[1].arguments[0].value).toBe( - 'search-session.attributes.status' - ); - expect(findInput.filter.arguments[1].arguments[1].value).toBe('in_progress'); - }); -}); diff --git a/src/plugins/data/server/search/session/check_persisted_sessions.ts b/src/plugins/data/server/search/session/check_persisted_sessions.ts deleted file mode 100644 index ace921a56a05..000000000000 --- a/src/plugins/data/server/search/session/check_persisted_sessions.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { EMPTY, Observable } from 'rxjs'; -import { catchError, concatMap } from 'rxjs/operators'; -import { nodeBuilder, KueryNode } from '@kbn/es-query'; -import { SEARCH_SESSION_TYPE, SearchSessionStatus } from '../../../common'; -import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page'; -import { CheckSearchSessionsDeps, SearchSessionsResponse } from './types'; -import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status'; -import { SearchSessionsConfigSchema } from '../../../config'; - -export const SEARCH_SESSIONS_TASK_TYPE = 'search_sessions_monitor'; -export const SEARCH_SESSIONS_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_TASK_TYPE}`; - -function checkPersistedSessionsPage( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema, - filter: KueryNode, - page: number -): Observable { - const { logger } = deps; - logger.debug(`${SEARCH_SESSIONS_TASK_TYPE} Fetching sessions from page ${page}`); - return getSearchSessionsPage$(deps, filter, config.pageSize, page).pipe( - concatMap(async (persistedSearchSessions) => { - if (!persistedSearchSessions.total) return persistedSearchSessions; - - logger.debug( - `${SEARCH_SESSIONS_TASK_TYPE} Found ${persistedSearchSessions.total} sessions, processing ${persistedSearchSessions.saved_objects.length}` - ); - - const updatedSessions = await getAllSessionsStatusUpdates( - deps, - config, - persistedSearchSessions - ); - await bulkUpdateSessions(deps, updatedSessions); - - return persistedSearchSessions; - }) - ); -} - -export function checkPersistedSessionsProgress( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema -) { - const { logger } = deps; - - const persistedSessionsFilter = nodeBuilder.and([ - nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'true'), - nodeBuilder.is( - `${SEARCH_SESSION_TYPE}.attributes.status`, - SearchSessionStatus.IN_PROGRESS.toString() - ), - ]); - - return checkSearchSessionsByPage( - checkPersistedSessionsPage, - deps, - config, - persistedSessionsFilter - ).pipe( - catchError((e) => { - logger.error(`${SEARCH_SESSIONS_TASK_TYPE} Error while processing sessions: ${e?.message}`); - return EMPTY; - }) - ); -} diff --git a/src/plugins/data/server/search/session/expire_persisted_sessions.ts b/src/plugins/data/server/search/session/expire_persisted_sessions.ts deleted file mode 100644 index 1c7ad8ff3e34..000000000000 --- a/src/plugins/data/server/search/session/expire_persisted_sessions.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { EMPTY, Observable } from 'rxjs'; -import { catchError, concatMap } from 'rxjs/operators'; -import { nodeBuilder, KueryNode } from '@kbn/es-query'; -import { SEARCH_SESSION_TYPE, SearchSessionStatus } from '../../../common'; -import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page'; -import { CheckSearchSessionsDeps, SearchSessionsResponse } from './types'; -import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status'; -import { SearchSessionsConfigSchema } from '../../../config'; - -export const SEARCH_SESSIONS_EXPIRE_TASK_TYPE = 'search_sessions_expire'; -export const SEARCH_SESSIONS_EXPIRE_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_EXPIRE_TASK_TYPE}`; - -function checkSessionExpirationPage( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema, - filter: KueryNode, - page: number -): Observable { - const { logger } = deps; - logger.debug(`${SEARCH_SESSIONS_EXPIRE_TASK_TYPE} Fetching sessions from page ${page}`); - return getSearchSessionsPage$(deps, filter, config.pageSize, page).pipe( - concatMap(async (searchSessions) => { - if (!searchSessions.total) return searchSessions; - - logger.debug( - `${SEARCH_SESSIONS_EXPIRE_TASK_TYPE} Found ${searchSessions.total} sessions, processing ${searchSessions.saved_objects.length}` - ); - - const updatedSessions = await getAllSessionsStatusUpdates(deps, config, searchSessions); - await bulkUpdateSessions(deps, updatedSessions); - - return searchSessions; - }) - ); -} - -export function checkPersistedCompletedSessionExpiration( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema -) { - const { logger } = deps; - - const persistedSessionsFilter = nodeBuilder.and([ - nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'true'), - nodeBuilder.is( - `${SEARCH_SESSION_TYPE}.attributes.status`, - SearchSessionStatus.COMPLETE.toString() - ), - ]); - - return checkSearchSessionsByPage( - checkSessionExpirationPage, - deps, - config, - persistedSessionsFilter - ).pipe( - catchError((e) => { - logger.error( - `${SEARCH_SESSIONS_EXPIRE_TASK_TYPE} Error while processing sessions: ${e?.message}` - ); - return EMPTY; - }) - ); -} diff --git a/src/plugins/data/server/search/session/get_search_session_page.test.ts b/src/plugins/data/server/search/session/get_search_session_page.test.ts deleted file mode 100644 index 43a8f987dc8f..000000000000 --- a/src/plugins/data/server/search/session/get_search_session_page.test.ts +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page'; -import { ENHANCED_ES_SEARCH_STRATEGY, SearchSessionStatus } from '../../../common'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { SearchStatus } from './types'; -import moment from 'moment'; -import { SavedObjectsClientContract } from '@kbn/core/server'; -import { of, Subject, throwError } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { SearchSessionsConfigSchema } from '../../../config'; - -jest.useFakeTimers(); - -describe('checkSearchSessionsByPage', () => { - const mockClient = {} as any; - let savedObjectsClient: jest.Mocked; - const config: SearchSessionsConfigSchema = { - enabled: true, - pageSize: 5, - management: {} as any, - } as any; - const mockLogger: any = { - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - const emptySO = { - attributes: { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(10, 's')), - idMapping: {}, - }, - }; - - beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); - }); - - describe('getSearchSessionsPage$', () => { - test('sorting is by "touched"', async () => { - savedObjectsClient.find.mockResolvedValueOnce({ - saved_objects: [], - total: 0, - } as any); - - await getSearchSessionsPage$( - { - savedObjectsClient, - } as any, - { - type: 'literal', - }, - 1, - 1 - ); - - expect(savedObjectsClient.find).toHaveBeenCalledWith( - expect.objectContaining({ sortField: 'touched', sortOrder: 'asc' }) - ); - }); - }); - - describe('pagination', () => { - test('fetches one page if got empty response', async () => { - const checkFn = jest.fn().mockReturnValue(of(undefined)); - - await checkSearchSessionsByPage( - checkFn, - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config, - [] - ).toPromise(); - - expect(checkFn).toHaveBeenCalledTimes(1); - }); - - test('fetches one page if got response with no saved objects', async () => { - const checkFn = jest.fn().mockReturnValue( - of({ - total: 0, - }) - ); - - await checkSearchSessionsByPage( - checkFn, - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config, - [] - ).toPromise(); - - expect(checkFn).toHaveBeenCalledTimes(1); - }); - - test('fetches one page if less than page size object are returned', async () => { - const checkFn = jest.fn().mockReturnValue( - of({ - saved_objects: [emptySO, emptySO], - total: 5, - }) - ); - - await checkSearchSessionsByPage( - checkFn, - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config, - [] - ).toPromise(); - - expect(checkFn).toHaveBeenCalledTimes(1); - }); - - test('fetches two pages if exactly page size objects are returned', async () => { - let i = 0; - - const checkFn = jest.fn().mockImplementation(() => - of({ - saved_objects: i++ === 0 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [], - total: 5, - page: i, - }) - ); - - await checkSearchSessionsByPage( - checkFn, - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config, - [] - ).toPromise(); - - expect(checkFn).toHaveBeenCalledTimes(2); - - // validate that page number increases - const page1 = checkFn.mock.calls[0][3]; - const page2 = checkFn.mock.calls[1][3]; - expect(page1).toBe(1); - expect(page2).toBe(2); - }); - - test('fetches two pages if page size +1 objects are returned', async () => { - let i = 0; - - const checkFn = jest.fn().mockImplementation(() => - of({ - saved_objects: i++ === 0 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [emptySO], - total: i === 0 ? 5 : 1, - page: i, - }) - ); - - await checkSearchSessionsByPage( - checkFn, - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config, - [] - ).toPromise(); - - expect(checkFn).toHaveBeenCalledTimes(2); - }); - - test('sessions fetched in the beginning are processed even if sessions in the end fail', async () => { - let i = 0; - - const checkFn = jest.fn().mockImplementation(() => { - if (++i === 2) { - return throwError('Fake find error...'); - } - return of({ - saved_objects: - i <= 5 - ? [ - i === 1 - ? { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(2, 'm')), - idMapping: { - 'map-key': { - strategy: ENHANCED_ES_SEARCH_STRATEGY, - id: 'async-id', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - } - : emptySO, - emptySO, - emptySO, - emptySO, - emptySO, - ] - : [], - total: 25, - page: i, - }); - }); - - await checkSearchSessionsByPage( - checkFn, - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config, - [] - ) - .toPromise() - .catch(() => {}); - - expect(checkFn).toHaveBeenCalledTimes(2); - }); - - test('fetching is abortable', async () => { - let i = 0; - const abort$ = new Subject(); - - const checkFn = jest.fn().mockImplementation(() => { - if (++i === 2) { - abort$.next(); - } - - return of({ - saved_objects: i <= 5 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [], - total: 25, - page: i, - }); - }); - - await checkSearchSessionsByPage( - checkFn, - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - config, - [] - ) - .pipe(takeUntil(abort$)) - .toPromise() - .catch(() => {}); - - jest.runAllTimers(); - - // if not for `abort$` then this would be called 6 times! - expect(checkFn).toHaveBeenCalledTimes(2); - }); - }); -}); diff --git a/src/plugins/data/server/search/session/get_search_session_page.ts b/src/plugins/data/server/search/session/get_search_session_page.ts deleted file mode 100644 index d98d513c9e73..000000000000 --- a/src/plugins/data/server/search/session/get_search_session_page.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SavedObjectsClientContract, Logger } from '@kbn/core/server'; -import { from, Observable, EMPTY } from 'rxjs'; -import { concatMap } from 'rxjs/operators'; -import type { KueryNode } from '@kbn/es-query'; -import { SearchSessionSavedObjectAttributes, SEARCH_SESSION_TYPE } from '../../../common'; -import { CheckSearchSessionsDeps, CheckSearchSessionsFn } from './types'; -import { SearchSessionsConfigSchema } from '../../../config'; - -export interface GetSessionsDeps { - savedObjectsClient: SavedObjectsClientContract; - logger: Logger; -} - -export function getSearchSessionsPage$( - { savedObjectsClient }: GetSessionsDeps, - filter: KueryNode, - pageSize: number, - page: number -) { - return from( - savedObjectsClient.find({ - page, - perPage: pageSize, - type: SEARCH_SESSION_TYPE, - namespaces: ['*'], - // process older sessions first - sortField: 'touched', - sortOrder: 'asc', - filter, - }) - ); -} - -export const checkSearchSessionsByPage = ( - checkFn: CheckSearchSessionsFn, - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema, - filters: any, - nextPage = 1 -): Observable => - checkFn(deps, config, filters, nextPage).pipe( - concatMap((result) => { - if (!result || !result.saved_objects || result.saved_objects.length < config.pageSize) { - return EMPTY; - } else { - // TODO: while processing previous page session list might have been changed and we might skip a session, - // because it would appear now on a different "page". - // This isn't critical, as we would pick it up on a next task iteration, but maybe we could improve this somehow - return checkSearchSessionsByPage(checkFn, deps, config, filters, result.page + 1); - } - }) - ); diff --git a/src/plugins/data/server/search/session/get_search_status.ts b/src/plugins/data/server/search/session/get_search_status.ts index 3ded923376b1..fb34fff1c1d4 100644 --- a/src/plugins/data/server/search/session/get_search_status.ts +++ b/src/plugins/data/server/search/session/get_search_status.ts @@ -9,24 +9,25 @@ import { i18n } from '@kbn/i18n'; import type { TransportResult } from '@elastic/elasticsearch'; import { ElasticsearchClient } from '@kbn/core/server'; -import { SearchSessionRequestInfo } from '../../../common'; -import { AsyncSearchStatusResponse } from '../..'; +import { SearchSessionRequestStatus } from '../../../common'; import { SearchStatus } from './types'; +import { AsyncSearchStatusResponse } from '../..'; export async function getSearchStatus( - client: ElasticsearchClient, + internalClient: ElasticsearchClient, asyncId: string -): Promise> { +): Promise { // TODO: Handle strategies other than the default one // https://github.com/elastic/kibana/issues/127880 try { // @ts-expect-error start_time_in_millis: EpochMillis is string | number - const apiResponse: TransportResult = await client.asyncSearch.status( - { - id: asyncId, - }, - { meta: true } - ); + const apiResponse: TransportResult = + await internalClient.asyncSearch.status( + { + id: asyncId, + }, + { meta: true } + ); const response = apiResponse.body; if ((response.is_partial && !response.is_running) || response.completion_status >= 400) { return { diff --git a/src/plugins/data/server/search/session/get_session_status.test.ts b/src/plugins/data/server/search/session/get_session_status.test.ts index db75e322edda..b7e323a15f06 100644 --- a/src/plugins/data/server/search/session/get_session_status.test.ts +++ b/src/plugins/data/server/search/session/get_session_status.test.ts @@ -6,72 +6,141 @@ * Side Public License, v 1. */ -import { SearchStatus } from './types'; +import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import { getSessionStatus } from './get_session_status'; -import { SearchSessionStatus } from '../../../common'; +import { SearchSessionSavedObjectAttributes, SearchSessionStatus } from '../../../common'; import moment from 'moment'; import { SearchSessionsConfigSchema } from '../../../config'; +const mockInProgressSearchResponse = { + body: { + is_partial: true, + is_running: true, + }, +}; + +const mockErrorSearchResponse = { + body: { + is_partial: false, + is_running: false, + completion_status: 500, + }, +}; + +const mockCompletedSearchResponse = { + body: { + is_partial: false, + is_running: false, + completion_status: 200, + }, +}; + describe('getSessionStatus', () => { - const mockConfig = { - notTouchedInProgressTimeout: moment.duration(1, 'm'), - } as unknown as SearchSessionsConfigSchema; - test("returns an in_progress status if there's nothing inside the session", () => { + beforeEach(() => { + deps.internalClient.asyncSearch.status.mockReset(); + }); + + const mockConfig = {} as unknown as SearchSessionsConfigSchema; + const deps = { internalClient: elasticsearchServiceMock.createElasticsearchClient() }; + test("returns an in_progress status if there's nothing inside the session", async () => { const session: any = { idMapping: {}, touched: moment(), }; - expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS); + expect(await getSessionStatus(deps, session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS); }); - test("returns an error status if there's at least one error", () => { + test("returns an error status if there's at least one error", async () => { + deps.internalClient.asyncSearch.status.mockImplementation(async ({ id }): Promise => { + switch (id) { + case 'a': + return mockInProgressSearchResponse; + case 'b': + return mockErrorSearchResponse; + case 'c': + return mockCompletedSearchResponse; + default: + // eslint-disable-next-line no-console + console.error('Not mocked search id'); + throw new Error('Not mocked search id'); + } + }); const session: any = { idMapping: { - a: { status: SearchStatus.IN_PROGRESS }, - b: { status: SearchStatus.ERROR, error: 'Nope' }, - c: { status: SearchStatus.COMPLETE }, + a: { + id: 'a', + }, + b: { id: 'b' }, + c: { id: 'c' }, }, }; - expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.ERROR); + expect(await getSessionStatus(deps, session, mockConfig)).toBe(SearchSessionStatus.ERROR); }); - test('expires a empty session after a minute', () => { + test('expires a session if expired < now', async () => { const session: any = { idMapping: {}, - touched: moment().subtract(2, 'm'), + expires: moment().subtract(2, 'm'), }; - expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.EXPIRED); + + expect(await getSessionStatus(deps, session, mockConfig)).toBe(SearchSessionStatus.EXPIRED); }); - test('doesnt expire a full session after a minute', () => { + test('doesnt expire if expire > now', async () => { + deps.internalClient.asyncSearch.status.mockResolvedValue(mockInProgressSearchResponse as any); + const session: any = { idMapping: { - a: { status: SearchStatus.IN_PROGRESS }, + a: { id: 'a' }, }, - touched: moment().subtract(2, 'm'), + expires: moment().add(2, 'm'), }; - expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS); + expect(await getSessionStatus(deps, session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS); }); - test('returns a complete status if all are complete', () => { + test('returns cancelled status if session was cancelled', async () => { + const session: Partial = { + idMapping: { + a: { id: 'a', strategy: 'ese' }, + }, + isCanceled: true, + expires: moment().subtract(2, 'm').toISOString(), + }; + expect( + await getSessionStatus(deps, session as SearchSessionSavedObjectAttributes, mockConfig) + ).toBe(SearchSessionStatus.CANCELLED); + }); + + test('returns a complete status if all are complete', async () => { + deps.internalClient.asyncSearch.status.mockResolvedValue(mockCompletedSearchResponse as any); + const session: any = { idMapping: { - a: { status: SearchStatus.COMPLETE }, - b: { status: SearchStatus.COMPLETE }, - c: { status: SearchStatus.COMPLETE }, + a: { id: 'a' }, + b: { id: 'b' }, + c: { id: 'c' }, }, }; - expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.COMPLETE); + expect(await getSessionStatus(deps, session, mockConfig)).toBe(SearchSessionStatus.COMPLETE); }); - test('returns a running status if some are still running', () => { + test('returns a running status if some are still running', async () => { + deps.internalClient.asyncSearch.status.mockImplementation(async ({ id }): Promise => { + switch (id) { + case 'a': + return mockInProgressSearchResponse; + default: + return mockCompletedSearchResponse; + } + }); + const session: any = { idMapping: { - a: { status: SearchStatus.IN_PROGRESS }, - b: { status: SearchStatus.COMPLETE }, - c: { status: SearchStatus.IN_PROGRESS }, + a: { id: 'a' }, + b: { id: 'b' }, + c: { id: 'c' }, }, }; - expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS); + expect(await getSessionStatus(deps, session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS); }); }); diff --git a/src/plugins/data/server/search/session/get_session_status.ts b/src/plugins/data/server/search/session/get_session_status.ts index 4614ea92318e..b89b4c487c32 100644 --- a/src/plugins/data/server/search/session/get_session_status.ts +++ b/src/plugins/data/server/search/session/get_session_status.ts @@ -7,25 +7,40 @@ */ import moment from 'moment'; +import { ElasticsearchClient } from '@kbn/core/server'; import { SearchSessionSavedObjectAttributes, SearchSessionStatus } from '../../../common'; import { SearchStatus } from './types'; import { SearchSessionsConfigSchema } from '../../../config'; +import { getSearchStatus } from './get_search_status'; -export function getSessionStatus( +export async function getSessionStatus( + deps: { internalClient: ElasticsearchClient }, session: SearchSessionSavedObjectAttributes, config: SearchSessionsConfigSchema -): SearchSessionStatus { - const searchStatuses = Object.values(session.idMapping); - const curTime = moment(); +): Promise { + if (session.isCanceled === true) { + return SearchSessionStatus.CANCELLED; + } + + const now = moment(); + + if (moment(session.expires).isBefore(now)) { + return SearchSessionStatus.EXPIRED; + } + + const searches = Object.values(session.idMapping); + const searchStatuses = await Promise.all( + searches.map(async (s) => { + const status = await getSearchStatus(deps.internalClient, s.id); + return { + ...s, + ...status, + }; + }) + ); + if (searchStatuses.some((item) => item.status === SearchStatus.ERROR)) { return SearchSessionStatus.ERROR; - } else if ( - searchStatuses.length === 0 && - curTime.diff(moment(session.touched), 'ms') > - moment.duration(config.notTouchedInProgressTimeout).asMilliseconds() - ) { - // Expire empty sessions that weren't touched for a minute - return SearchSessionStatus.EXPIRED; } else if ( searchStatuses.length > 0 && searchStatuses.every((item) => item.status === SearchStatus.COMPLETE) diff --git a/src/plugins/data/server/search/session/mocks.ts b/src/plugins/data/server/search/session/mocks.ts index 33715810060a..339ef628356d 100644 --- a/src/plugins/data/server/search/session/mocks.ts +++ b/src/plugins/data/server/search/session/mocks.ts @@ -22,6 +22,7 @@ export function createSearchSessionsClientMock(): jest.Mocked ({ diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 6565eaf11d3b..b44b9a08b8d3 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -11,17 +11,16 @@ import { SavedObjectsClientContract, SavedObjectsErrorHelpers, } from '@kbn/core/server'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { ElasticsearchClientMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; import { nodeBuilder } from '@kbn/es-query'; import { SearchSessionService } from './session_service'; import { createRequestHash } from './utils'; import moment from 'moment'; import { coreMock } from '@kbn/core/server/mocks'; import { ConfigSchema } from '../../../config'; -import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; import { SEARCH_SESSION_TYPE, SearchSessionStatus } from '../../../common'; -import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; +import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; const MAX_UPDATE_RETRIES = 3; @@ -29,8 +28,8 @@ const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); describe('SearchSessionService', () => { let savedObjectsClient: jest.Mocked; + let elasticsearchClient: ElasticsearchClientMock; let service: SearchSessionService; - let mockTaskManager: jest.Mocked; const MOCK_STRATEGY = 'ese'; @@ -67,19 +66,14 @@ describe('SearchSessionService', () => { describe('Feature disabled', () => { beforeEach(async () => { savedObjectsClient = savedObjectsClientMock.create(); + elasticsearchClient = elasticsearchServiceMock.createElasticsearchClient(); const config: ConfigSchema = { search: { sessions: { enabled: false, - pageSize: 10000, - notTouchedInProgressTimeout: moment.duration(1, 'm'), notTouchedTimeout: moment.duration(2, 'm'), maxUpdateRetries: MAX_UPDATE_RETRIES, defaultExpiration: moment.duration(7, 'd'), - monitoringTaskTimeout: moment.duration(5, 'm'), - cleanupInterval: moment.duration(10, 's'), - trackingInterval: moment.duration(10, 's'), - expireInterval: moment.duration(10, 'm'), management: {} as any, }, }, @@ -90,23 +84,14 @@ describe('SearchSessionService', () => { error: jest.fn(), }; service = new SearchSessionService(mockLogger, config, '8.0.0'); - service.setup(coreMock.createSetup(), { taskManager: taskManagerMock.createSetup() }); - const coreStart = coreMock.createStart(); - mockTaskManager = taskManagerMock.createStart(); + service.setup(coreMock.createSetup(), {}); await flushPromises(); - await service.start(coreStart, { - taskManager: mockTaskManager, - }); }); afterEach(() => { service.stop(); }); - it('task is cleared, if exists', async () => { - expect(mockTaskManager.removeIfExists).toHaveBeenCalled(); - }); - it('trackId ignores', async () => { await service.trackId({ savedObjectsClient }, mockUser1, { params: {} }, '123', { sessionId: '321', @@ -148,19 +133,15 @@ describe('SearchSessionService', () => { describe('Feature enabled', () => { beforeEach(async () => { savedObjectsClient = savedObjectsClientMock.create(); + elasticsearchClient = elasticsearchServiceMock.createElasticsearchClient(); const config: ConfigSchema = { search: { sessions: { enabled: true, pageSize: 10000, - notTouchedInProgressTimeout: moment.duration(1, 'm'), notTouchedTimeout: moment.duration(2, 'm'), maxUpdateRetries: MAX_UPDATE_RETRIES, defaultExpiration: moment.duration(7, 'd'), - trackingInterval: moment.duration(10, 's'), - expireInterval: moment.duration(10, 'm'), - monitoringTaskTimeout: moment.duration(5, 'm'), - cleanupInterval: moment.duration(10, 's'), management: {} as any, }, }, @@ -171,24 +152,17 @@ describe('SearchSessionService', () => { error: jest.fn(), }; service = new SearchSessionService(mockLogger, config, '8.0.0'); - service.setup(coreMock.createSetup(), { taskManager: taskManagerMock.createSetup() }); + service.setup(coreMock.createSetup(), {}); const coreStart = coreMock.createStart(); - mockTaskManager = taskManagerMock.createStart(); + await flushPromises(); - await service.start(coreStart, { - taskManager: mockTaskManager, - }); + await service.start(coreStart, {}); }); afterEach(() => { service.stop(); }); - it('task is cleared and re-created', async () => { - expect(mockTaskManager.removeIfExists).toHaveBeenCalled(); - expect(mockTaskManager.ensureScheduled).toHaveBeenCalled(); - }); - describe('save', () => { it('throws if `name` is not provided', () => { expect(() => @@ -198,7 +172,9 @@ describe('SearchSessionService', () => { it('throws if `appId` is not provided', () => { expect( - service.save({ savedObjectsClient }, mockUser1, sessionId, { name: 'banana' }) + service.save({ savedObjectsClient }, mockUser1, sessionId, { + name: 'banana', + }) ).rejects.toMatchInlineSnapshot(`[Error: AppId is required]`); }); @@ -232,8 +208,6 @@ describe('SearchSessionService', () => { expect(type).toBe(SEARCH_SESSION_TYPE); expect(id).toBe(sessionId); expect(callAttributes).not.toHaveProperty('idMapping'); - expect(callAttributes).toHaveProperty('touched'); - expect(callAttributes).toHaveProperty('persisted', true); expect(callAttributes).toHaveProperty('name', 'banana'); expect(callAttributes).toHaveProperty('appId', 'nanana'); expect(callAttributes).toHaveProperty('locatorId', 'panama'); @@ -265,10 +239,8 @@ describe('SearchSessionService', () => { expect(type).toBe(SEARCH_SESSION_TYPE); expect(options?.id).toBe(sessionId); expect(callAttributes).toHaveProperty('idMapping', {}); - expect(callAttributes).toHaveProperty('touched'); expect(callAttributes).toHaveProperty('expires'); expect(callAttributes).toHaveProperty('created'); - expect(callAttributes).toHaveProperty('persisted', true); expect(callAttributes).toHaveProperty('name', 'banana'); expect(callAttributes).toHaveProperty('appId', 'nanana'); expect(callAttributes).toHaveProperty('locatorId', 'panama'); @@ -343,13 +315,20 @@ describe('SearchSessionService', () => { total: 1, per_page: 1, page: 0, + statuses: { + [mockSavedObject.id]: { status: SearchSessionStatus.IN_PROGRESS }, + }, }; savedObjectsClient.find.mockResolvedValue(mockResponse); const options = { page: 0, perPage: 5 }; - const response = await service.find({ savedObjectsClient }, mockUser1, options); + const response = await service.find( + { savedObjectsClient, internalElasticsearchClient: elasticsearchClient }, + mockUser1, + options + ); - expect(response).toBe(mockResponse); + expect(response).toEqual(mockResponse); const [[findOptions]] = savedObjectsClient.find.mock.calls; expect(findOptions).toMatchInlineSnapshot(` Object { @@ -424,17 +403,28 @@ describe('SearchSessionService', () => { total: 1, per_page: 1, page: 0, + statuses: { + [mockSavedObject.id]: { status: SearchSessionStatus.IN_PROGRESS }, + }, }; savedObjectsClient.find.mockResolvedValue(mockResponse); const options1 = { filter: 'foobar' }; - const response1 = await service.find({ savedObjectsClient }, mockUser1, options1); + const response1 = await service.find( + { savedObjectsClient, internalElasticsearchClient: elasticsearchClient }, + mockUser1, + options1 + ); const options2 = { filter: nodeBuilder.is('foo', 'bar') }; - const response2 = await service.find({ savedObjectsClient }, mockUser1, options2); + const response2 = await service.find( + { savedObjectsClient, internalElasticsearchClient: elasticsearchClient }, + mockUser1, + options2 + ); - expect(response1).toBe(mockResponse); - expect(response2).toBe(mockResponse); + expect(response1).toEqual(mockResponse); + expect(response2).toEqual(mockResponse); const [[findOptions1], [findOptions2]] = savedObjectsClient.find.mock.calls; expect(findOptions1).toMatchInlineSnapshot(` @@ -599,13 +589,20 @@ describe('SearchSessionService', () => { total: 1, per_page: 1, page: 0, + statuses: { + [mockSavedObject.id]: { status: SearchSessionStatus.IN_PROGRESS }, + }, }; savedObjectsClient.find.mockResolvedValue(mockResponse); const options = { page: 0, perPage: 5 }; - const response = await service.find({ savedObjectsClient }, null, options); + const response = await service.find( + { savedObjectsClient, internalElasticsearchClient: elasticsearchClient }, + null, + options + ); - expect(response).toBe(mockResponse); + expect(response).toEqual(mockResponse); const [[findOptions]] = savedObjectsClient.find.mock.calls; expect(findOptions).toMatchInlineSnapshot(` Object { @@ -642,7 +639,6 @@ describe('SearchSessionService', () => { expect(type).toBe(SEARCH_SESSION_TYPE); expect(id).toBe(sessionId); expect(callAttributes).toHaveProperty('name', attributes.name); - expect(callAttributes).toHaveProperty('touched'); }); it('throws if user conflicts', () => { @@ -675,7 +671,6 @@ describe('SearchSessionService', () => { expect(type).toBe(SEARCH_SESSION_TYPE); expect(id).toBe(sessionId); expect(callAttributes).toHaveProperty('name', 'new_name'); - expect(callAttributes).toHaveProperty('touched'); }); }); @@ -688,8 +683,7 @@ describe('SearchSessionService', () => { expect(type).toBe(SEARCH_SESSION_TYPE); expect(id).toBe(sessionId); - expect(callAttributes).toHaveProperty('status', SearchSessionStatus.CANCELLED); - expect(callAttributes).toHaveProperty('touched'); + expect(callAttributes).toHaveProperty('isCanceled', true); }); it('throws if user conflicts', () => { @@ -709,8 +703,7 @@ describe('SearchSessionService', () => { expect(type).toBe(SEARCH_SESSION_TYPE); expect(id).toBe(sessionId); - expect(callAttributes).toHaveProperty('status', SearchSessionStatus.CANCELLED); - expect(callAttributes).toHaveProperty('touched'); + expect(callAttributes).toHaveProperty('isCanceled', true); }); }); @@ -740,11 +733,9 @@ describe('SearchSessionService', () => { expect(callAttributes).toHaveProperty('idMapping', { [requestHash]: { id: searchId, - status: SearchSessionStatus.IN_PROGRESS, strategy: MOCK_STRATEGY, }, }); - expect(callAttributes).toHaveProperty('touched'); }); it('retries updating the saved object if there was a ES conflict 409', async () => { @@ -827,15 +818,12 @@ describe('SearchSessionService', () => { expect(callAttributes).toHaveProperty('idMapping', { [requestHash]: { id: searchId, - status: SearchSessionStatus.IN_PROGRESS, strategy: MOCK_STRATEGY, }, }); expect(callAttributes).toHaveProperty('expires'); expect(callAttributes).toHaveProperty('created'); - expect(callAttributes).toHaveProperty('touched'); expect(callAttributes).toHaveProperty('sessionId', sessionId); - expect(callAttributes).toHaveProperty('persisted', false); }); it('retries updating if update returned 404 and then update returned conflict 409 (first create race condition)', async () => { @@ -939,16 +927,13 @@ describe('SearchSessionService', () => { expect(callAttributes1).toHaveProperty('idMapping', { [requestHash1]: { id: searchId1, - status: SearchSessionStatus.IN_PROGRESS, strategy: MOCK_STRATEGY, }, [requestHash2]: { id: searchId2, - status: SearchSessionStatus.IN_PROGRESS, strategy: MOCK_STRATEGY, }, }); - expect(callAttributes1).toHaveProperty('touched'); const [type2, id2, callAttributes2] = savedObjectsClient.update.mock.calls[1]; expect(type2).toBe(SEARCH_SESSION_TYPE); @@ -956,11 +941,9 @@ describe('SearchSessionService', () => { expect(callAttributes2).toHaveProperty('idMapping', { [requestHash3]: { id: searchId3, - status: SearchSessionStatus.IN_PROGRESS, strategy: MOCK_STRATEGY, }, }); - expect(callAttributes2).toHaveProperty('touched'); }); }); diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index e46c9bc80a2c..d82956d19f73 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -8,67 +8,48 @@ import { notFound } from '@hapi/boom'; import { debounce } from 'lodash'; -import { nodeBuilder, fromKueryExpression } from '@kbn/es-query'; +import { fromKueryExpression, nodeBuilder } from '@kbn/es-query'; import { CoreSetup, CoreStart, KibanaRequest, - SavedObjectsClientContract, Logger, SavedObject, - SavedObjectsFindOptions, + SavedObjectsClientContract, SavedObjectsErrorHelpers, + SavedObjectsFindOptions, + ElasticsearchClient, } from '@kbn/core/server'; import type { AuthenticatedUser, SecurityPluginSetup } from '@kbn/security-plugin/server'; -import type { - TaskManagerSetupContract, - TaskManagerStartContract, -} from '@kbn/task-manager-plugin/server'; import { + ENHANCED_ES_SEARCH_STRATEGY, IKibanaSearchRequest, ISearchOptions, - ENHANCED_ES_SEARCH_STRATEGY, SEARCH_SESSION_TYPE, SearchSessionRequestInfo, SearchSessionSavedObjectAttributes, - SearchSessionStatus, + SearchSessionsFindResponse, + SearchSessionStatusResponse, } from '../../../common'; import { ISearchSessionService, NoSearchIdInSessionError } from '../..'; import { createRequestHash } from './utils'; import { ConfigSchema, SearchSessionsConfigSchema } from '../../../config'; -import { - registerSearchSessionsTask, - scheduleSearchSessionsTask, - unscheduleSearchSessionsTask, -} from './setup_task'; -import { SearchStatus } from './types'; -import { - checkPersistedSessionsProgress, - SEARCH_SESSIONS_TASK_ID, - SEARCH_SESSIONS_TASK_TYPE, -} from './check_persisted_sessions'; -import { - SEARCH_SESSIONS_CLEANUP_TASK_TYPE, - checkNonPersistedSessions, - SEARCH_SESSIONS_CLEANUP_TASK_ID, -} from './check_non_persisted_sessions'; -import { - SEARCH_SESSIONS_EXPIRE_TASK_TYPE, - SEARCH_SESSIONS_EXPIRE_TASK_ID, - checkPersistedCompletedSessionExpiration, -} from './expire_persisted_sessions'; +import { getSessionStatus } from './get_session_status'; export interface SearchSessionDependencies { savedObjectsClient: SavedObjectsClientContract; } + +export interface SearchSessionStatusDependencies extends SearchSessionDependencies { + internalElasticsearchClient: ElasticsearchClient; +} + interface SetupDependencies { - taskManager: TaskManagerSetupContract; security?: SecurityPluginSetup; } -interface StartDependencies { - taskManager: TaskManagerStartContract; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface StartDependencies {} const DEBOUNCE_UPDATE_OR_CREATE_WAIT = 1000; const DEBOUNCE_UPDATE_OR_CREATE_MAX_WAIT = 5000; @@ -101,83 +82,17 @@ export class SearchSessionService implements ISearchSessionService { public setup(core: CoreSetup, deps: SetupDependencies) { this.security = deps.security; - const taskDeps = { - config: this.config, - taskManager: deps.taskManager, - logger: this.logger, - }; - - registerSearchSessionsTask( - core, - taskDeps, - SEARCH_SESSIONS_TASK_TYPE, - 'persisted session progress', - checkPersistedSessionsProgress - ); - - registerSearchSessionsTask( - core, - taskDeps, - SEARCH_SESSIONS_CLEANUP_TASK_TYPE, - 'non persisted session cleanup', - checkNonPersistedSessions - ); - - registerSearchSessionsTask( - core, - taskDeps, - SEARCH_SESSIONS_EXPIRE_TASK_TYPE, - 'complete session expiration', - checkPersistedCompletedSessionExpiration - ); this.setupCompleted = true; } - public async start(core: CoreStart, deps: StartDependencies) { + public start(core: CoreStart, deps: StartDependencies) { if (!this.setupCompleted) throw new Error('SearchSessionService setup() must be called before start()'); - - return this.setupMonitoring(core, deps); } public stop() {} - private setupMonitoring = async (core: CoreStart, deps: StartDependencies) => { - const taskDeps = { - config: this.config, - taskManager: deps.taskManager, - logger: this.logger, - }; - - if (this.sessionConfig.enabled) { - scheduleSearchSessionsTask( - taskDeps, - SEARCH_SESSIONS_TASK_ID, - SEARCH_SESSIONS_TASK_TYPE, - this.sessionConfig.trackingInterval - ); - - scheduleSearchSessionsTask( - taskDeps, - SEARCH_SESSIONS_CLEANUP_TASK_ID, - SEARCH_SESSIONS_CLEANUP_TASK_TYPE, - this.sessionConfig.cleanupInterval - ); - - scheduleSearchSessionsTask( - taskDeps, - SEARCH_SESSIONS_EXPIRE_TASK_ID, - SEARCH_SESSIONS_EXPIRE_TASK_TYPE, - this.sessionConfig.expireInterval - ); - } else { - unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_TASK_ID); - unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_CLEANUP_TASK_ID); - unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_EXPIRE_TASK_ID); - } - }; - private processUpdateOrCreateBatchQueue = debounce( () => { const queue = [...this.updateOrCreateBatchQueue]; @@ -304,7 +219,6 @@ export class SearchSessionService implements ISearchSessionService { locatorId, initialState, restoreState, - persisted: true, }); }; @@ -324,14 +238,11 @@ export class SearchSessionService implements ISearchSessionService { SEARCH_SESSION_TYPE, { sessionId, - status: SearchSessionStatus.IN_PROGRESS, expires: new Date( Date.now() + this.sessionConfig.defaultExpiration.asMilliseconds() ).toISOString(), created: new Date().toISOString(), - touched: new Date().toISOString(), idMapping: {}, - persisted: false, version: this.version, realmType, realmName, @@ -353,14 +264,15 @@ export class SearchSessionService implements ISearchSessionService { sessionId ); this.throwOnUserConflict(user, session); + return session; }; - public find = ( - { savedObjectsClient }: SearchSessionDependencies, + public find = async ( + { savedObjectsClient, internalElasticsearchClient }: SearchSessionStatusDependencies, user: AuthenticatedUser | null, options: Omit - ) => { + ): Promise => { const userFilters = user === null ? [] @@ -378,11 +290,31 @@ export class SearchSessionService implements ISearchSessionService { const filterKueryNode = typeof options.filter === 'string' ? fromKueryExpression(options.filter) : options.filter; const filter = nodeBuilder.and(userFilters.concat(filterKueryNode ?? [])); - return savedObjectsClient.find({ + const findResponse = await savedObjectsClient.find({ ...options, filter, type: SEARCH_SESSION_TYPE, }); + + const sessionStatuses = await Promise.all( + findResponse.saved_objects.map(async (so) => { + const sessionStatus = await getSessionStatus( + { internalClient: internalElasticsearchClient }, + so.attributes, + this.sessionConfig + ); + + return sessionStatus; + }) + ); + + return { + ...findResponse, + statuses: sessionStatuses.reduce((res, status, index) => { + res[findResponse.saved_objects[index].id] = { status }; + return res; + }, {} as Record), + }; }; public update = async ( @@ -399,7 +331,6 @@ export class SearchSessionService implements ISearchSessionService { sessionId, { ...attributes, - touched: new Date().toISOString(), } ); }; @@ -419,9 +350,9 @@ export class SearchSessionService implements ISearchSessionService { user: AuthenticatedUser | null, sessionId: string ) => { - this.logger.debug(`delete | ${sessionId}`); + this.logger.debug(`cancel | ${sessionId}`); return this.update(deps, user, sessionId, { - status: SearchSessionStatus.CANCELLED, + isCanceled: true, }); }; @@ -445,8 +376,9 @@ export class SearchSessionService implements ISearchSessionService { user: AuthenticatedUser | null, searchRequest: IKibanaSearchRequest, searchId: string, - { sessionId, strategy = ENHANCED_ES_SEARCH_STRATEGY }: ISearchOptions + options: ISearchOptions ) => { + const { sessionId, strategy = ENHANCED_ES_SEARCH_STRATEGY } = options; if (!this.sessionConfig.enabled || !sessionId || !searchId) return; this.logger.debug(`trackId | ${sessionId} | ${searchId}`); @@ -454,10 +386,9 @@ export class SearchSessionService implements ISearchSessionService { if (searchRequest.params) { const requestHash = createRequestHash(searchRequest.params); - const searchInfo = { + const searchInfo: SearchSessionRequestInfo = { id: searchId, strategy, - status: SearchStatus.IN_PROGRESS, }; idMapping = { [requestHash]: searchInfo }; } @@ -471,6 +402,7 @@ export class SearchSessionService implements ISearchSessionService { sessionId: string ) { const searchSession = await this.get(deps, user, sessionId); + const searchIdMapping = new Map(); Object.values(searchSession.attributes.idMapping).forEach((requestInfo) => { searchIdMapping.set(requestInfo.id, requestInfo.strategy); @@ -478,6 +410,23 @@ export class SearchSessionService implements ISearchSessionService { return searchIdMapping; } + public async status( + deps: SearchSessionStatusDependencies, + user: AuthenticatedUser | null, + sessionId: string + ): Promise { + this.logger.debug(`status | ${sessionId}`); + const session = await this.get(deps, user, sessionId); + + const sessionStatus = await getSessionStatus( + { internalClient: deps.internalElasticsearchClient }, + session.attributes, + this.sessionConfig + ); + + return { status: sessionStatus }; + } + /** * Look up an existing search ID that matches the given request in the given session so that the * request can continue rather than restart. @@ -510,13 +459,15 @@ export class SearchSessionService implements ISearchSessionService { return session.attributes.idMapping[requestHash].id; }; - public asScopedProvider = ({ savedObjects }: CoreStart) => { + public asScopedProvider = ({ savedObjects, elasticsearch }: CoreStart) => { return (request: KibanaRequest) => { const user = this.security?.authc.getCurrentUser(request) ?? null; const savedObjectsClient = savedObjects.getScopedClient(request, { includedHiddenTypes: [SEARCH_SESSION_TYPE], }); - const deps = { savedObjectsClient }; + + const internalElasticsearchClient = elasticsearch.client.asScoped(request).asInternalUser; + const deps = { savedObjectsClient, internalElasticsearchClient }; return { getId: this.getId.bind(this, deps, user), trackId: this.trackId.bind(this, deps, user), @@ -528,6 +479,7 @@ export class SearchSessionService implements ISearchSessionService { extend: this.extend.bind(this, deps, user), cancel: this.cancel.bind(this, deps, user), delete: this.delete.bind(this, deps, user), + status: this.status.bind(this, deps, user), getConfig: () => this.config.search.sessions, }; }; diff --git a/src/plugins/data/server/search/session/setup_task.ts b/src/plugins/data/server/search/session/setup_task.ts deleted file mode 100644 index 5fe44b0901b7..000000000000 --- a/src/plugins/data/server/search/session/setup_task.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Duration } from 'moment'; -import { filter, takeUntil } from 'rxjs/operators'; -import { BehaviorSubject } from 'rxjs'; -import type { RunContext, TaskRunCreatorFunction } from '@kbn/task-manager-plugin/server'; -import { CoreSetup, SavedObjectsClient } from '@kbn/core/server'; -import { SEARCH_SESSION_TYPE } from '../../../common'; -import { - SearchSessionTaskSetupDeps, - SearchSessionTaskStartDeps, - SearchSessionTaskFn, -} from './types'; - -export function searchSessionTaskRunner( - core: CoreSetup, - deps: SearchSessionTaskSetupDeps, - title: string, - checkFn: SearchSessionTaskFn -): TaskRunCreatorFunction { - const { logger, config } = deps; - return ({ taskInstance }: RunContext) => { - const aborted$ = new BehaviorSubject(false); - return { - async run() { - try { - const sessionConfig = config.search.sessions; - const [coreStart] = await core.getStartServices(); - if (!sessionConfig.enabled) { - logger.debug(`Search sessions are disabled. Skipping task ${title}.`); - return; - } - if (aborted$.getValue()) return; - - const internalRepo = coreStart.savedObjects.createInternalRepository([ - SEARCH_SESSION_TYPE, - ]); - const internalSavedObjectsClient = new SavedObjectsClient(internalRepo); - await checkFn( - { - logger, - client: coreStart.elasticsearch.client.asInternalUser, - savedObjectsClient: internalSavedObjectsClient, - }, - sessionConfig - ) - .pipe(takeUntil(aborted$.pipe(filter((aborted) => aborted)))) - .toPromise(); - - return { - state: {}, - }; - } catch (e) { - logger.error(`An error occurred. Skipping task ${title}.`); - } - }, - cancel: async () => { - aborted$.next(true); - }, - }; - }; -} - -export function registerSearchSessionsTask( - core: CoreSetup, - deps: SearchSessionTaskSetupDeps, - taskType: string, - title: string, - checkFn: SearchSessionTaskFn -) { - deps.taskManager.registerTaskDefinitions({ - [taskType]: { - title, - createTaskRunner: searchSessionTaskRunner(core, deps, title, checkFn), - timeout: `${deps.config.search.sessions.monitoringTaskTimeout.asSeconds()}s`, - }, - }); -} - -export async function unscheduleSearchSessionsTask( - { taskManager, logger }: SearchSessionTaskStartDeps, - taskId: string -) { - try { - await taskManager.removeIfExists(taskId); - logger.debug(`${taskId} cleared`); - } catch (e) { - logger.error(`${taskId} Error clearing task ${e.message}`); - } -} - -export async function scheduleSearchSessionsTask( - { taskManager, logger }: SearchSessionTaskStartDeps, - taskId: string, - taskType: string, - interval: Duration -) { - await taskManager.removeIfExists(taskId); - - try { - await taskManager.ensureScheduled({ - id: taskId, - taskType, - schedule: { - interval: `${interval.asSeconds()}s`, - }, - state: {}, - params: {}, - }); - - logger.debug(`${taskId} scheduled to run`); - } catch (e) { - logger.error(`${taskId} Error scheduling task ${e.message}`); - } -} diff --git a/src/plugins/data/server/search/session/types.ts b/src/plugins/data/server/search/session/types.ts index e39a33774f07..76d5f3002873 100644 --- a/src/plugins/data/server/search/session/types.ts +++ b/src/plugins/data/server/search/session/types.ts @@ -6,26 +6,23 @@ * Side Public License, v 1. */ -import { Observable } from 'rxjs'; import { CoreStart, KibanaRequest, SavedObject, SavedObjectsFindOptions, - SavedObjectsFindResponse, SavedObjectsUpdateResponse, - ElasticsearchClient, - Logger, - SavedObjectsClientContract, } from '@kbn/core/server'; -import type { - TaskManagerSetupContract, - TaskManagerStartContract, -} from '@kbn/task-manager-plugin/server'; -import { KueryNode } from '@kbn/es-query'; -import { SearchSessionSavedObjectAttributes } from '../../../common'; -import { IKibanaSearchRequest, ISearchOptions } from '../../../common/search'; -import { SearchSessionsConfigSchema, ConfigSchema } from '../../../config'; +import { + IKibanaSearchRequest, + ISearchOptions, + SearchSessionsFindResponse, + SearchSessionSavedObjectAttributes, + SearchSessionStatusResponse, +} from '../../../common/search'; +import { SearchSessionsConfigSchema } from '../../../config'; + +export { SearchStatus } from '../../../common/search'; export interface IScopedSearchSessionsClient { getId: (request: IKibanaSearchRequest, options: ISearchOptions) => Promise; @@ -40,9 +37,7 @@ export interface IScopedSearchSessionsClient { attributes: Partial ) => Promise | undefined>; get: (sessionId: string) => Promise>; - find: ( - options: Omit - ) => Promise>; + find: (options: Omit) => Promise; update: ( sessionId: string, attributes: Partial @@ -53,50 +48,10 @@ export interface IScopedSearchSessionsClient { sessionId: string, expires: Date ) => Promise>; + status: (sessionId: string) => Promise; getConfig: () => SearchSessionsConfigSchema | null; } export interface ISearchSessionService { asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient; } - -export enum SearchStatus { - IN_PROGRESS = 'in_progress', - ERROR = 'error', - COMPLETE = 'complete', -} - -export interface CheckSearchSessionsDeps { - savedObjectsClient: SavedObjectsClientContract; - client: ElasticsearchClient; - logger: Logger; -} - -export interface SearchSessionTaskSetupDeps { - taskManager: TaskManagerSetupContract; - logger: Logger; - config: ConfigSchema; -} - -export interface SearchSessionTaskStartDeps { - taskManager: TaskManagerStartContract; - logger: Logger; - config: ConfigSchema; -} - -export type SearchSessionTaskFn = ( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema -) => Observable; - -export type SearchSessionsResponse = SavedObjectsFindResponse< - SearchSessionSavedObjectAttributes, - unknown ->; - -export type CheckSearchSessionsFn = ( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema, - filter: KueryNode, - page: number -) => Observable; diff --git a/src/plugins/data/server/search/session/update_session_status.test.ts b/src/plugins/data/server/search/session/update_session_status.test.ts deleted file mode 100644 index 38e8ec6cad15..000000000000 --- a/src/plugins/data/server/search/session/update_session_status.test.ts +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { bulkUpdateSessions, updateSessionStatus } from './update_session_status'; -import { SearchSessionStatus, SearchSessionSavedObjectAttributes } from '../../../common'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { SearchStatus } from './types'; -import moment from 'moment'; -import { - SavedObjectsBulkUpdateObject, - SavedObjectsClientContract, - SavedObjectsFindResult, -} from '@kbn/core/server'; - -describe('bulkUpdateSessions', () => { - let mockClient: any; - const mockConfig: any = {}; - let savedObjectsClient: jest.Mocked; - const mockLogger: any = { - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); - mockClient = { - asyncSearch: { - status: jest.fn(), - delete: jest.fn(), - }, - eql: { - status: jest.fn(), - delete: jest.fn(), - }, - }; - }); - - describe('updateSessionStatus', () => { - test('updates expired session', async () => { - const so: SavedObjectsFindResult = { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - expires: moment().subtract(moment.duration(5, 'd')), - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - } as any; - - const updated = await updateSessionStatus( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - mockConfig, - so - ); - - expect(updated).toBeTruthy(); - expect(so.attributes.status).toBe(SearchSessionStatus.EXPIRED); - }); - - test('does nothing if the search is still running', async () => { - const so = { - id: '123', - attributes: { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(10, 's')), - expires: moment().add(moment.duration(5, 'd')), - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - } as any; - - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: true, - is_running: true, - }, - }); - - const updated = await updateSessionStatus( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - mockConfig, - so - ); - - expect(updated).toBeFalsy(); - expect(so.attributes.status).toBe(SearchSessionStatus.IN_PROGRESS); - }); - - test("doesn't re-check completed or errored searches", async () => { - const so = { - id: '123', - attributes: { - expires: moment().add(moment.duration(5, 'd')), - status: SearchSessionStatus.ERROR, - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.COMPLETE, - }, - 'another-search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.ERROR, - }, - }, - }, - } as any; - - const updated = await updateSessionStatus( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - mockConfig, - so - ); - - expect(updated).toBeFalsy(); - expect(mockClient.asyncSearch.status).not.toBeCalled(); - }); - - test('updates to complete if the search is done', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - attributes: { - status: SearchSessionStatus.IN_PROGRESS, - touched: '123', - expires: moment().add(moment.duration(5, 'd')), - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - } as any; - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: false, - is_running: false, - completion_status: 200, - }, - }); - - const updated = await updateSessionStatus( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - mockConfig, - so - ); - - expect(updated).toBeTruthy(); - - expect(mockClient.asyncSearch.status).toBeCalledWith({ id: 'search-id' }, { meta: true }); - expect(so.attributes.status).toBe(SearchSessionStatus.COMPLETE); - expect(so.attributes.status).toBe(SearchSessionStatus.COMPLETE); - expect(so.attributes.touched).not.toBe('123'); - expect(so.attributes.completed).not.toBeUndefined(); - expect(so.attributes.idMapping['search-hash'].status).toBe(SearchStatus.COMPLETE); - expect(so.attributes.idMapping['search-hash'].error).toBeUndefined(); - }); - - test('updates to error if the search is errored', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - attributes: { - expires: moment().add(moment.duration(5, 'd')), - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - } as any; - - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: false, - is_running: false, - completion_status: 500, - }, - }); - - const updated = await updateSessionStatus( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - mockConfig, - so - ); - - expect(updated).toBeTruthy(); - expect(so.attributes.status).toBe(SearchSessionStatus.ERROR); - expect(so.attributes.touched).not.toBe('123'); - expect(so.attributes.idMapping['search-hash'].status).toBe(SearchStatus.ERROR); - expect(so.attributes.idMapping['search-hash'].error).toBe( - 'Search completed with a 500 status' - ); - }); - }); - - describe('bulkUpdateSessions', () => { - test('does nothing if there are no open sessions', async () => { - await bulkUpdateSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - [] - ); - - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - expect(savedObjectsClient.delete).not.toBeCalled(); - }); - - test('updates in space', async () => { - const so = { - namespaces: ['awesome'], - attributes: { - expires: moment().add(moment.duration(5, 'd')), - status: SearchSessionStatus.IN_PROGRESS, - touched: '123', - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - } as any; - - savedObjectsClient.bulkUpdate = jest.fn().mockResolvedValue({ - saved_objects: [so], - }); - - await bulkUpdateSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - [so] - ); - - const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - const updatedAttributes = updateInput[0] as SavedObjectsBulkUpdateObject; - expect(updatedAttributes.namespace).toBe('awesome'); - }); - - test('logs failures', async () => { - const so = { - namespaces: ['awesome'], - attributes: { - expires: moment().add(moment.duration(5, 'd')), - status: SearchSessionStatus.IN_PROGRESS, - touched: '123', - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, - }, - }, - } as any; - - savedObjectsClient.bulkUpdate = jest.fn().mockResolvedValue({ - saved_objects: [ - { - error: 'nope', - }, - ], - }); - - await bulkUpdateSessions( - { - savedObjectsClient, - client: mockClient, - logger: mockLogger, - }, - [so] - ); - - expect(savedObjectsClient.bulkUpdate).toBeCalledTimes(1); - expect(mockLogger.error).toBeCalledTimes(1); - }); - }); -}); diff --git a/src/plugins/data/server/search/session/update_session_status.ts b/src/plugins/data/server/search/session/update_session_status.ts deleted file mode 100644 index e8405eb5427b..000000000000 --- a/src/plugins/data/server/search/session/update_session_status.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SavedObjectsFindResult, SavedObjectsUpdateResponse } from '@kbn/core/server'; -import { SearchSessionsConfigSchema } from '../../../config'; -import { - SearchSessionRequestInfo, - SearchSessionSavedObjectAttributes, - SearchSessionStatus, -} from '../../../common'; -import { getSearchStatus } from './get_search_status'; -import { getSessionStatus } from './get_session_status'; -import { CheckSearchSessionsDeps, SearchSessionsResponse, SearchStatus } from './types'; -import { isSearchSessionExpired } from './utils'; - -export async function updateSessionStatus( - { logger, client }: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema, - session: SavedObjectsFindResult -) { - let sessionUpdated = false; - const isExpired = isSearchSessionExpired(session); - - if (!isExpired) { - // Check statuses of all running searches - await Promise.all( - Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { - const updateSearchRequest = ( - currentStatus: Pick - ) => { - sessionUpdated = true; - session.attributes.idMapping[searchKey] = { - ...session.attributes.idMapping[searchKey], - ...currentStatus, - }; - }; - - const searchInfo = session.attributes.idMapping[searchKey]; - if (searchInfo.status === SearchStatus.IN_PROGRESS) { - try { - const currentStatus = await getSearchStatus(client, searchInfo.id); - - if (currentStatus.status !== searchInfo.status) { - logger.debug(`search ${searchInfo.id} | status changed to ${currentStatus.status}`); - updateSearchRequest(currentStatus); - } - } catch (e) { - logger.error(e); - updateSearchRequest({ - status: SearchStatus.ERROR, - error: e.message || e.meta.error?.caused_by?.reason, - }); - } - } - }) - ); - } - - // And only then derive the session's status - const sessionStatus = isExpired - ? SearchSessionStatus.EXPIRED - : getSessionStatus(session.attributes, config); - if (sessionStatus !== session.attributes.status) { - const now = new Date().toISOString(); - session.attributes.status = sessionStatus; - session.attributes.touched = now; - if (sessionStatus === SearchSessionStatus.COMPLETE) { - session.attributes.completed = now; - } else if (session.attributes.completed) { - session.attributes.completed = null; - } - sessionUpdated = true; - } - - return sessionUpdated; -} - -export async function getAllSessionsStatusUpdates( - deps: CheckSearchSessionsDeps, - config: SearchSessionsConfigSchema, - searchSessions: SearchSessionsResponse -) { - const updatedSessions = new Array>(); - - await Promise.all( - searchSessions.saved_objects.map(async (session) => { - const updated = await updateSessionStatus(deps, config, session); - - if (updated) { - updatedSessions.push(session); - } - }) - ); - - return updatedSessions; -} - -export async function bulkUpdateSessions( - { logger, savedObjectsClient }: CheckSearchSessionsDeps, - updatedSessions: Array> -) { - if (updatedSessions.length) { - // If there's an error, we'll try again in the next iteration, so there's no need to check the output. - const updatedResponse = await savedObjectsClient.bulkUpdate( - updatedSessions.map((session) => ({ - ...session, - namespace: session.namespaces?.[0], - })) - ); - - const success: Array> = []; - const fail: Array> = []; - - updatedResponse.saved_objects.forEach((savedObjectResponse) => { - if ('error' in savedObjectResponse) { - fail.push(savedObjectResponse); - logger.error( - `Error while updating search session ${savedObjectResponse?.id}: ${savedObjectResponse.error?.message}` - ); - } else { - success.push(savedObjectResponse); - } - }); - - logger.debug(`Updating search sessions: success: ${success.length}, fail: ${fail.length}`); - } -} diff --git a/src/plugins/data/server/search/strategies/common/async_utils.test.ts b/src/plugins/data/server/search/strategies/common/async_utils.test.ts index 7c90a0fd4c12..9771a042f187 100644 --- a/src/plugins/data/server/search/strategies/common/async_utils.test.ts +++ b/src/plugins/data/server/search/strategies/common/async_utils.test.ts @@ -21,7 +21,7 @@ const getMockSearchSessionsConfig = ({ describe('request utils', () => { describe('getCommonDefaultAsyncSubmitParams', () => { - test('Uses `keep_alive` from default params if no `sessionId` is provided', async () => { + test('Uses short `keep_alive` if no `sessionId` is provided', async () => { const mockConfig = getMockSearchSessionsConfig({ defaultExpiration: moment.duration(3, 'd'), }); @@ -29,13 +29,24 @@ describe('request utils', () => { expect(params).toHaveProperty('keep_alive', '1m'); }); - test('Uses `keep_alive` from config if enabled', async () => { + test('Uses short `keep_alive` if sessions enabled but no yet saved', async () => { const mockConfig = getMockSearchSessionsConfig({ defaultExpiration: moment.duration(3, 'd'), }); const params = getCommonDefaultAsyncSubmitParams(mockConfig, { sessionId: 'foo', }); + expect(params).toHaveProperty('keep_alive', '1m'); + }); + + test('Uses `keep_alive` from config if sessions enabled and session is saved', async () => { + const mockConfig = getMockSearchSessionsConfig({ + defaultExpiration: moment.duration(3, 'd'), + }); + const params = getCommonDefaultAsyncSubmitParams(mockConfig, { + sessionId: 'foo', + isStored: true, + }); expect(params).toHaveProperty('keep_alive', '259200000ms'); }); @@ -89,12 +100,51 @@ describe('request utils', () => { expect(params).toHaveProperty('keep_alive', '1m'); }); - test('Has no `keep_alive` if `sessionId` is provided', async () => { + test('Has short `keep_alive` if `sessionId` is provided', async () => { const mockConfig = getMockSearchSessionsConfig({ defaultExpiration: moment.duration(3, 'd'), enabled: true, }); const params = getCommonDefaultAsyncGetParams(mockConfig, { sessionId: 'foo' }); + expect(params).toHaveProperty('keep_alive', '1m'); + }); + + test('Has `keep_alive` from config if `sessionId` is provided and session is stored', async () => { + const mockConfig = getMockSearchSessionsConfig({ + defaultExpiration: moment.duration(3, 'd'), + enabled: true, + }); + const params = getCommonDefaultAsyncGetParams(mockConfig, { + sessionId: 'foo', + isStored: true, + }); + expect(params).toHaveProperty('keep_alive', '259200000ms'); + }); + + test("Don't extend keepAlive if search has already been extended", async () => { + const mockConfig = getMockSearchSessionsConfig({ + defaultExpiration: moment.duration(3, 'd'), + enabled: true, + }); + const params = getCommonDefaultAsyncGetParams(mockConfig, { + sessionId: 'foo', + isStored: true, + isSearchStored: true, + }); + expect(params).not.toHaveProperty('keep_alive'); + }); + + test("Don't extend keepAlive if search is being restored", async () => { + const mockConfig = getMockSearchSessionsConfig({ + defaultExpiration: moment.duration(3, 'd'), + enabled: true, + }); + const params = getCommonDefaultAsyncGetParams(mockConfig, { + sessionId: 'foo', + isStored: true, + isSearchStored: false, + isRestore: true, + }); expect(params).not.toHaveProperty('keep_alive'); }); diff --git a/src/plugins/data/server/search/strategies/common/async_utils.ts b/src/plugins/data/server/search/strategies/common/async_utils.ts index 46483ca3f327..c0af68f915bd 100644 --- a/src/plugins/data/server/search/strategies/common/async_utils.ts +++ b/src/plugins/data/server/search/strategies/common/async_utils.ts @@ -25,9 +25,10 @@ export function getCommonDefaultAsyncSubmitParams( > { const useSearchSessions = searchSessionsConfig?.enabled && !!options.sessionId; - const keepAlive = useSearchSessions - ? `${searchSessionsConfig!.defaultExpiration.asMilliseconds()}ms` - : '1m'; + const keepAlive = + useSearchSessions && options.isStored + ? `${searchSessionsConfig!.defaultExpiration.asMilliseconds()}ms` + : '1m'; return { // Wait up to 100ms for the response to return @@ -51,9 +52,13 @@ export function getCommonDefaultAsyncGetParams( return { // Wait up to 100ms for the response to return wait_for_completion_timeout: '100ms', - ...(useSearchSessions - ? // Don't change the expiration of search requests that are tracked in a search session - undefined + ...(useSearchSessions && options.isStored + ? // Use session's keep_alive if search belongs to a stored session + options.isSearchStored || options.isRestore // if search was already stored and extended, then no need to extend keepAlive + ? {} + : { + keep_alive: `${searchSessionsConfig!.defaultExpiration.asMilliseconds()}ms`, + } : { // We still need to do polling for searches not within the context of a search session or when search session disabled keep_alive: '1m', diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts index 409c84a4638f..c2c42f1ff896 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts @@ -205,7 +205,7 @@ describe('ES search strategy', () => { }); describe('with sessionId', () => { - it('makes a POST request with params (long keepalive)', async () => { + it('Submit search with session id that is not saved creates a search with short keep_alive', async () => { mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; @@ -218,10 +218,26 @@ describe('ES search strategy', () => { expect(request.index).toEqual(params.index); expect(request.body).toEqual(params.body); + expect(request).toHaveProperty('keep_alive', '1m'); + }); + + it('Submit search with session id and session is saved creates a search with long keep_alive', async () => { + mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger); + + await esSearch.search({ params }, { sessionId: '1', isStored: true }, mockDeps).toPromise(); + + expect(mockSubmitCaller).toBeCalled(); + const request = mockSubmitCaller.mock.calls[0][0]; + expect(request.index).toEqual(params.index); + expect(request.body).toEqual(params.body); + expect(request).toHaveProperty('keep_alive', '604800000ms'); }); - it('makes a GET request to async search without keepalive', async () => { + it('makes a GET request to async search with short keepalive, if session is not saved', async () => { mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; @@ -229,6 +245,44 @@ describe('ES search strategy', () => { await esSearch.search({ id: 'foo', params }, { sessionId: '1' }, mockDeps).toPromise(); + expect(mockGetCaller).toBeCalled(); + const request = mockGetCaller.mock.calls[0][0]; + expect(request.id).toEqual('foo'); + expect(request).toHaveProperty('wait_for_completion_timeout'); + expect(request).toHaveProperty('keep_alive', '1m'); + }); + + it('makes a GET request to async search with long keepalive, if session is saved', async () => { + mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger); + + await esSearch + .search({ id: 'foo', params }, { sessionId: '1', isStored: true }, mockDeps) + .toPromise(); + + expect(mockGetCaller).toBeCalled(); + const request = mockGetCaller.mock.calls[0][0]; + expect(request.id).toEqual('foo'); + expect(request).toHaveProperty('wait_for_completion_timeout'); + expect(request).toHaveProperty('keep_alive', '604800000ms'); + }); + + it('makes a GET request to async search with no keepalive, if session is session saved and search is stored', async () => { + mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger); + + await esSearch + .search( + { id: 'foo', params }, + { sessionId: '1', isSearchStored: true, isStored: true }, + mockDeps + ) + .toPromise(); + expect(mockGetCaller).toBeCalled(); const request = mockGetCaller.mock.calls[0][0]; expect(request.id).toEqual('foo'); diff --git a/src/plugins/data/server/search/strategies/ese_search/request_utils.test.ts b/src/plugins/data/server/search/strategies/ese_search/request_utils.test.ts index 878806d9b355..b908a1df4f0e 100644 --- a/src/plugins/data/server/search/strategies/ese_search/request_utils.test.ts +++ b/src/plugins/data/server/search/strategies/ese_search/request_utils.test.ts @@ -60,7 +60,7 @@ describe('request utils', () => { expect(params).toHaveProperty('keep_alive', '1m'); }); - test('Uses `keep_alive` from config if enabled', async () => { + test('Uses `keep_alive` from config if enabled and session is stored', async () => { const mockUiSettingsClient = getMockUiSettingsClient({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false, }); @@ -69,6 +69,7 @@ describe('request utils', () => { }); const params = await getDefaultAsyncSubmitParams(mockUiSettingsClient, mockConfig, { sessionId: 'foo', + isStored: true, }); expect(params).toHaveProperty('keep_alive', '259200000ms'); }); @@ -132,12 +133,16 @@ describe('request utils', () => { expect(params).toHaveProperty('keep_alive', '1m'); }); - test('Has no `keep_alive` if `sessionId` is provided', async () => { + test('Has no `keep_alive` if `sessionId` is provided and search already stored', async () => { const mockConfig = getMockSearchSessionsConfig({ defaultExpiration: moment.duration(3, 'd'), enabled: true, }); - const params = getDefaultAsyncGetParams(mockConfig, { sessionId: 'foo' }); + const params = getDefaultAsyncGetParams(mockConfig, { + sessionId: 'foo', + isStored: true, + isSearchStored: true, + }); expect(params).not.toHaveProperty('keep_alive'); }); diff --git a/src/plugins/data/server/search/strategies/sql_search/request_utils.test.ts b/src/plugins/data/server/search/strategies/sql_search/request_utils.test.ts index 9944de7be17b..d37bcfb0655f 100644 --- a/src/plugins/data/server/search/strategies/sql_search/request_utils.test.ts +++ b/src/plugins/data/server/search/strategies/sql_search/request_utils.test.ts @@ -29,12 +29,13 @@ describe('request utils', () => { expect(params).toHaveProperty('keep_alive', '1m'); }); - test('Uses `keep_alive` from config if enabled', async () => { + test('Uses `keep_alive` from config if enabled and session is stored', async () => { const mockConfig = getMockSearchSessionsConfig({ defaultExpiration: moment.duration(3, 'd'), }); const params = getDefaultAsyncSubmitParams(mockConfig, { sessionId: 'foo', + isStored: true, }); expect(params).toHaveProperty('keep_alive', '259200000ms'); }); @@ -89,12 +90,16 @@ describe('request utils', () => { expect(params).toHaveProperty('keep_alive', '1m'); }); - test('Has no `keep_alive` if `sessionId` is provided', async () => { + test('Has no `keep_alive` if `sessionId` is provided, search and session are stored', async () => { const mockConfig = getMockSearchSessionsConfig({ defaultExpiration: moment.duration(3, 'd'), enabled: true, }); - const params = getDefaultAsyncGetParams(mockConfig, { sessionId: 'foo' }); + const params = getDefaultAsyncGetParams(mockConfig, { + sessionId: 'foo', + isStored: true, + isSearchStored: true, + }); expect(params).not.toHaveProperty('keep_alive'); }); diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 2d2a10300ee6..50fc29334d22 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -89,6 +89,7 @@ export interface IScopedSearchClient extends ISearchClient { cancelSession: IScopedSearchSessionsClient['cancel']; deleteSession: IScopedSearchSessionsClient['delete']; extendSession: IScopedSearchSessionsClient['extend']; + getSessionStatus: IScopedSearchSessionsClient['status']; } export interface ISearchStart< diff --git a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx index 08a13c7f43d4..98f5f906113b 100644 --- a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx +++ b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx @@ -7,7 +7,14 @@ */ import React, { useState, useEffect, useCallback, useRef } from 'react'; -import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; +import { + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiLoadingSpinner, + EuiLink, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import memoizeOne from 'memoize-one'; import { @@ -67,6 +74,7 @@ export interface Props { defaultTypeIsRollup?: boolean; requireTimestampField?: boolean; editData?: DataView; + showManagementLink?: boolean; allowAdHoc: boolean; } @@ -85,11 +93,14 @@ const IndexPatternEditorFlyoutContentComponent = ({ requireTimestampField = false, editData, allowAdHoc, + showManagementLink, }: Props) => { const { - services: { http, dataViews, uiSettings, overlays }, + services: { application, http, dataViews, uiSettings, overlays }, } = useKibana(); + const canSave = dataViews.getCanSaveSync(); + const { form } = useForm({ // Prefill with data if editData exists defaultValue: { @@ -97,7 +108,7 @@ const IndexPatternEditorFlyoutContentComponent = ({ isAdHoc: false, ...(editData ? { - title: editData.title, + title: editData.getIndexPattern(), id: editData.id, name: editData.name, ...(editData.timeFieldName @@ -131,7 +142,7 @@ const IndexPatternEditorFlyoutContentComponent = ({ }; } - if (editData && editData.title !== formData.title) { + if (editData && editData.getIndexPattern() !== formData.title) { editDataViewModal({ dataViewName: formData.name || formData.title, overlays, @@ -313,7 +324,7 @@ const IndexPatternEditorFlyoutContentComponent = ({ useEffect(() => { if (editData) { loadSources(); - reloadMatchedIndices(removeSpaces(editData.title)); + reloadMatchedIndices(removeSpaces(editData.getIndexPattern())); } // We use the below eslint-disable as adding 'loadSources' and 'reloadMatchedIndices' as a dependency creates an infinite loop // eslint-disable-next-line react-hooks/exhaustive-deps @@ -376,6 +387,17 @@ const IndexPatternEditorFlyoutContentComponent = ({

{editData ? editorTitleEditMode : editorTitle}

+ {showManagementLink && editData && editData.id && ( + + {i18n.translate('indexPatternEditor.goToManagementPage', { + defaultMessage: 'View on data view management page', + })} + + )}
{indexPatternTypeSelect} @@ -425,7 +447,9 @@ const IndexPatternEditorFlyoutContentComponent = ({ }} submitDisabled={form.isSubmitted && !form.isValid} isEdit={!!editData} + isPersisted={Boolean(editData && editData.isPersisted())} allowAdHoc={allowAdHoc} + canSave={canSave} /> diff --git a/src/plugins/data_view_editor/public/components/data_view_flyout_content_container.tsx b/src/plugins/data_view_editor/public/components/data_view_flyout_content_container.tsx index 35801d0a9dcf..3aaf56e6daaa 100644 --- a/src/plugins/data_view_editor/public/components/data_view_flyout_content_container.tsx +++ b/src/plugins/data_view_editor/public/components/data_view_flyout_content_container.tsx @@ -20,6 +20,7 @@ const IndexPatternFlyoutContentContainer = ({ requireTimestampField = false, editData, allowAdHocDataView, + showManagementLink, }: DataViewEditorProps) => { const { services: { dataViews, notifications }, @@ -30,7 +31,7 @@ const IndexPatternFlyoutContentContainer = ({ let saveResponse; if (editData) { const { name = '', timeFieldName, title = '' } = dataViewSpec; - editData.title = title; + editData.setIndexPattern(title); editData.name = name; editData.timeFieldName = timeFieldName; saveResponse = editData.isPersisted() @@ -68,6 +69,7 @@ const IndexPatternFlyoutContentContainer = ({ defaultTypeIsRollup={defaultTypeIsRollup} requireTimestampField={requireTimestampField} editData={editData} + showManagementLink={showManagementLink} allowAdHoc={allowAdHocDataView || false} /> ); diff --git a/src/plugins/data_view_editor/public/components/footer/footer.tsx b/src/plugins/data_view_editor/public/components/footer/footer.tsx index 01d954fb29bd..024885e91d54 100644 --- a/src/plugins/data_view_editor/public/components/footer/footer.tsx +++ b/src/plugins/data_view_editor/public/components/footer/footer.tsx @@ -22,7 +22,9 @@ interface FooterProps { onSubmit: (isAdHoc?: boolean) => void; submitDisabled: boolean; isEdit: boolean; + isPersisted: boolean; allowAdHoc: boolean; + canSave: boolean; } const closeButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutCloseButtonLabel', { @@ -37,11 +39,26 @@ const editButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutEditButt defaultMessage: 'Save', }); +const editUnpersistedButtonLabel = i18n.translate( + 'indexPatternEditor.editor.flyoutEditUnpersistedButtonLabel', + { + defaultMessage: 'Continue to use without saving', + } +); + const exploreButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutExploreButtonLabel', { defaultMessage: 'Use without saving', }); -export const Footer = ({ onCancel, onSubmit, submitDisabled, isEdit, allowAdHoc }: FooterProps) => { +export const Footer = ({ + onCancel, + onSubmit, + submitDisabled, + isEdit, + allowAdHoc, + isPersisted, + canSave, +}: FooterProps) => { const submitPersisted = () => { onSubmit(false); }; @@ -81,17 +98,23 @@ export const Footer = ({ onCancel, onSubmit, submitDisabled, isEdit, allowAdHoc )} - - - {isEdit ? editButtonLabel : saveButtonLabel} - - + {(canSave || (isEdit && !isPersisted)) && ( + + + {isEdit + ? isPersisted + ? editButtonLabel + : editUnpersistedButtonLabel + : saveButtonLabel} + + + )} diff --git a/src/plugins/data_view_editor/public/open_editor.tsx b/src/plugins/data_view_editor/public/open_editor.tsx index b422de8e40ce..29ea140daef4 100644 --- a/src/plugins/data_view_editor/public/open_editor.tsx +++ b/src/plugins/data_view_editor/public/open_editor.tsx @@ -35,6 +35,7 @@ export const getEditorOpener = notifications, application, dataViews, + overlays, searchClient, }); @@ -46,6 +47,7 @@ export const getEditorOpener = defaultTypeIsRollup = false, requireTimestampField = false, allowAdHocDataView = false, + editData, }: DataViewEditorProps): CloseEditor => { const closeEditor = () => { if (overlayRef) { @@ -72,9 +74,11 @@ export const getEditorOpener = closeEditor(); onCancel(); }} + editData={editData} defaultTypeIsRollup={defaultTypeIsRollup} requireTimestampField={requireTimestampField} allowAdHocDataView={allowAdHocDataView} + showManagementLink={Boolean(editData && editData.isPersisted())} /> , diff --git a/src/plugins/data_view_editor/public/plugin.tsx b/src/plugins/data_view_editor/public/plugin.tsx index b0186b838987..232958fbb2c2 100644 --- a/src/plugins/data_view_editor/public/plugin.tsx +++ b/src/plugins/data_view_editor/public/plugin.tsx @@ -21,7 +21,7 @@ export class DataViewEditorPlugin } public start(core: CoreStart, plugins: StartPlugins) { - const { application, uiSettings, docLinks, http, notifications } = core; + const { application, uiSettings, docLinks, http, notifications, overlays } = core; const { data, dataViews } = plugins; return { @@ -48,6 +48,7 @@ export class DataViewEditorPlugin http, notifications, application, + overlays, dataViews, searchClient: data.search.search, }} diff --git a/src/plugins/data_view_editor/public/types.ts b/src/plugins/data_view_editor/public/types.ts index 4500e522119d..b5e506db4d3e 100644 --- a/src/plugins/data_view_editor/public/types.ts +++ b/src/plugins/data_view_editor/public/types.ts @@ -13,6 +13,7 @@ import { NotificationsStart, DocLinksStart, HttpSetup, + OverlayStart, } from '@kbn/core/public'; import { EuiComboBoxOptionOption } from '@elastic/eui'; @@ -31,6 +32,7 @@ export interface DataViewEditorContext { http: HttpSetup; notifications: NotificationsStart; application: ApplicationStart; + overlays: OverlayStart; dataViews: DataViewsPublicPluginStart; searchClient: DataPublicPluginStart['search']['search']; } @@ -62,6 +64,11 @@ export interface DataViewEditorProps { * if set to true user is presented with an option to create ad-hoc dataview without a saved object. */ allowAdHocDataView?: boolean; + + /** + * if set to true a link to the management page is shown + */ + showManagementLink?: boolean; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/setup_environment.tsx b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/setup_environment.tsx index 4f7cc3e57a97..b28d8fdb7d2d 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/setup_environment.tsx +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/setup_environment.tsx @@ -118,6 +118,7 @@ export const WithFieldEditorDependencies = const dependencies: Context = { dataView: { title: indexPatternNameForTest, + getIndexPattern: () => indexPatternNameForTest, name: indexPatternNameForTest, getName: () => indexPatternNameForTest, fields: { getAll: spyIndexPatternGetAllFields }, diff --git a/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx b/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx index 34740187d77d..8366bcc71cb7 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx @@ -168,7 +168,9 @@ export const FieldEditorFlyoutContentContainer = ({ if (!editedField) { throw new Error( - `Unable to find field named '${updatedField.name}' on index pattern '${dataView.title}'` + `Unable to find field named '${ + updatedField.name + }' on index pattern '${dataView.getIndexPattern()}'` ); } diff --git a/src/plugins/data_view_field_editor/public/components/preview/field_preview_context.tsx b/src/plugins/data_view_field_editor/public/components/preview/field_preview_context.tsx index 6d6c38f8dfc6..a971d502643c 100644 --- a/src/plugins/data_view_field_editor/public/components/preview/field_preview_context.tsx +++ b/src/plugins/data_view_field_editor/public/components/preview/field_preview_context.tsx @@ -217,7 +217,7 @@ export const FieldPreviewProvider: FunctionComponent = ({ children }) => { const [response, searchError] = await search .search({ params: { - index: dataView.title, + index: dataView.getIndexPattern(), body: { size: limit, }, @@ -269,7 +269,7 @@ export const FieldPreviewProvider: FunctionComponent = ({ children }) => { const [response, searchError] = await search .search({ params: { - index: dataView.title, + index: dataView.getIndexPattern(), body: { size: 1, query: { diff --git a/src/plugins/data_view_field_editor/public/components/preview/field_preview_header.tsx b/src/plugins/data_view_field_editor/public/components/preview/field_preview_header.tsx index 6d327a7e0cdd..62d61dbdd4c7 100644 --- a/src/plugins/data_view_field_editor/public/components/preview/field_preview_header.tsx +++ b/src/plugins/data_view_field_editor/public/components/preview/field_preview_header.tsx @@ -49,7 +49,7 @@ export const FieldPreviewHeader = () => { {i18n.translate('indexPatternFieldEditor.fieldPreview.subTitle', { defaultMessage: 'From: {from}', values: { - from: from.value === 'cluster' ? dataView.title : i18nTexts.customData, + from: from.value === 'cluster' ? dataView.getIndexPattern() : i18nTexts.customData, }, })} diff --git a/src/plugins/data_views/README.mdx b/src/plugins/data_views/README.mdx index 6d2ef4c97bb3..930ee4e76b6a 100644 --- a/src/plugins/data_views/README.mdx +++ b/src/plugins/data_views/README.mdx @@ -1,17 +1,16 @@ --- -id: kibDataPlugin -slug: /kibana-dev-docs/services/data-plugin -title: Data services -image: https://source.unsplash.com/400x175/?Search -description: The data plugin contains services for searching, querying and filtering. -date: 2020-12-02 -tags: ['kibana', 'dev', 'contributor', 'api docs'] +id: kibDataViewsPlugin +slug: /kibana-dev-docs/services/data-views-plugin +title: Data Views services +description: The data views plugin contains services for dealing with Kibana's Data Views. +date: 2022-10-05 +tags: ['kibana', 'dev', 'contributor', 'api docs', 'data views', 'has data'] --- # Data Views The data views API provides a consistent method of structuring and formatting documents -and field lists across the various Kibana apps. Its typically used in conjunction with +and field lists across the various Kibana apps. It's typically used in conjunction with for composing queries. *Note: Kibana index patterns are currently being renamed to data views. There will be some naming inconsistencies until the transition is complete.* diff --git a/src/plugins/data_views/common/data_views/data_view.test.ts b/src/plugins/data_views/common/data_views/data_view.test.ts index 25966a1d9fc8..11176475f7c9 100644 --- a/src/plugins/data_views/common/data_views/data_view.test.ts +++ b/src/plugins/data_views/common/data_views/data_view.test.ts @@ -406,6 +406,19 @@ describe('IndexPattern', () => { }); }); + describe('getIndexPattern', () => { + test('should return the index pattern, labeled title on the data view spec', () => { + expect(indexPattern.getIndexPattern()).toBe( + stubbedSavedObjectIndexPattern().attributes.title + ); + }); + + test('setIndexPattern', () => { + indexPattern.setIndexPattern('test'); + expect(indexPattern.getIndexPattern()).toBe('test'); + }); + }); + describe('getFormatterForField', () => { test('should return the default one for empty objects', () => { indexPattern.setFieldFormat('scriptedFieldWithEmptyFormatter', {}); @@ -452,7 +465,7 @@ describe('IndexPattern', () => { metaFields: [], }); expect(restoredPattern.id).toEqual(indexPattern.id); - expect(restoredPattern.title).toEqual(indexPattern.title); + expect(restoredPattern.getIndexPattern()).toEqual(indexPattern.getIndexPattern()); expect(restoredPattern.timeFieldName).toEqual(indexPattern.timeFieldName); expect(restoredPattern.fields.length).toEqual(indexPattern.fields.length); }); 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 f7f6451ca045..428a5c4321c6 100644 --- a/src/plugins/data_views/common/data_views/data_view.ts +++ b/src/plugins/data_views/common/data_views/data_view.ts @@ -75,6 +75,7 @@ export class DataView implements DataViewBase { public id?: string; /** * Title of data view + * @deprecated use getIndexPattern instead */ public title: string = ''; /** @@ -193,6 +194,22 @@ export class DataView implements DataViewBase { */ getName = () => (this.name ? this.name : this.title); + /** + * Get index pattern + * @returns index pattern string + */ + + getIndexPattern = () => this.title; + + /** + * Set index pattern + * @param string index pattern string + */ + + setIndexPattern = (indexPattern: string) => { + this.title = indexPattern; + }; + /** * Get last saved saved object fields */ @@ -298,7 +315,7 @@ export class DataView implements DataViewBase { const spec: DataViewSpec = { id: this.id, version: this.version, - title: this.title, + title: this.getIndexPattern(), timeFieldName: this.timeFieldName, sourceFilters: [...(this.sourceFilters || [])], fields, @@ -412,7 +429,7 @@ export class DataView implements DataViewBase { return { fieldAttrs: fieldAttrs ? JSON.stringify(fieldAttrs) : undefined, - title: this.title, + title: this.getIndexPattern(), timeFieldName: this.timeFieldName, sourceFilters: this.sourceFilters ? JSON.stringify(this.sourceFilters) : undefined, fields: JSON.stringify(this.fields?.filter((field) => field.scripted) ?? []), 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 a096cbe07cd5..64e9ee483ef3 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 @@ -224,11 +224,11 @@ describe('IndexPatterns', () => { // This will conflict because samePattern did a save (from refreshFields) // but the resave should work fine - pattern.title = 'foo2'; + pattern.setIndexPattern('foo2'); await indexPatterns.updateSavedObject(pattern); // This should not be able to recover - samePattern.title = 'foo3'; + samePattern.setIndexPattern('foo3'); let result; try { @@ -241,18 +241,18 @@ describe('IndexPatterns', () => { }); test('create', async () => { - const title = 'kibana-*'; + const indexPattern = 'kibana-*'; indexPatterns.refreshFields = jest.fn(); - const indexPattern = await indexPatterns.create({ title }, true); - expect(indexPattern).toBeInstanceOf(DataView); - expect(indexPattern.title).toBe(title); + const dataView = await indexPatterns.create({ title: indexPattern }, true); + expect(dataView).toBeInstanceOf(DataView); + expect(dataView.getIndexPattern()).toBe(indexPattern); expect(indexPatterns.refreshFields).not.toBeCalled(); - await indexPatterns.create({ title }); + await indexPatterns.create({ title: indexPattern }); expect(indexPatterns.refreshFields).toBeCalled(); - expect(indexPattern.id).toBeDefined(); - expect(indexPattern.isPersisted()).toBe(false); + expect(dataView.id).toBeDefined(); + expect(dataView.isPersisted()).toBe(false); }); test('createSavedObject', async () => { @@ -267,14 +267,14 @@ describe('IndexPatterns', () => { version, attributes: { ...savedObject.attributes, - title: dataView.title, + title: dataView.getIndexPattern(), }, }); const indexPattern = await indexPatterns.createSavedObject(dataView); expect(indexPattern).toBeInstanceOf(DataView); expect(indexPattern.id).toBe(dataView.id); - expect(indexPattern.title).toBe(title); + expect(indexPattern.getIndexPattern()).toBe(title); expect(indexPattern.isPersisted()).toBe(true); }); diff --git a/src/plugins/data_views/common/data_views/data_views.ts b/src/plugins/data_views/common/data_views/data_views.ts index 892df9b312e8..fa0db477fcd7 100644 --- a/src/plugins/data_views/common/data_views/data_views.ts +++ b/src/plugins/data_views/common/data_views/data_views.ts @@ -512,7 +512,7 @@ export class DataViewsService { type: dataView.type, rollupIndex: dataView?.typeMeta?.params?.rollup_index, allowNoIndex: dataView.allowNoIndex, - pattern: dataView.title as string, + pattern: dataView.getIndexPattern(), metaFields, }); }; @@ -556,7 +556,7 @@ export class DataViewsService { if (err instanceof DataViewMissingIndices) { this.onNotification( { title: err.message, color: 'danger', iconType: 'alert' }, - `refreshFields:${indexPattern.title}` + `refreshFields:${indexPattern.getIndexPattern()}` ); } @@ -565,10 +565,10 @@ export class DataViewsService { { title: i18n.translate('dataViews.fetchFieldErrorTitle', { defaultMessage: 'Error fetching fields for data view {title} (ID: {id})', - values: { id: indexPattern.id, title: indexPattern.title }, + values: { id: indexPattern.id, title: indexPattern.getIndexPattern() }, }), }, - indexPattern.title + indexPattern.getIndexPattern() ); } }; @@ -1000,7 +1000,7 @@ export class DataViewsService { this.onNotification( { title, color: 'danger' }, - `updateSavedObject:${indexPattern.title}` + `updateSavedObject:${indexPattern.getIndexPattern()}` ); throw err; } 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 4912022e08fd..e7cc01fb00ea 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 @@ -18,16 +18,18 @@ import { import { i18n } from '@kbn/i18n'; import type { DataView } from '@kbn/data-views-plugin/public'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { + getVisualizeInformation, + triggerVisualizeActions, +} from '@kbn/unified-field-list-plugin/public'; import { HitsCounter } from '../hits_counter'; import { GetStateReturn } from '../../services/discover_state'; import { DiscoverHistogram } from './histogram'; import { DataCharts$, DataTotalHits$ } from '../../hooks/use_saved_search'; import { useChartPanels } from './use_chart_panels'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; -import { - getVisualizeInformation, - triggerVisualizeActions, -} from '../sidebar/lib/visualize_trigger_utils'; +import { getUiActions } from '../../../../kibana_services'; +import { PLUGIN_ID } from '../../../../../common'; const DiscoverHistogramMemoized = memo(DiscoverHistogram); export const CHART_HIDDEN_KEY = 'discover:chartHidden'; @@ -72,7 +74,13 @@ export function DiscoverChart({ useEffect(() => { if (!timeField) return; - getVisualizeInformation(timeField, dataView, savedSearch.columns || []).then((info) => { + getVisualizeInformation( + getUiActions(), + timeField, + dataView, + savedSearch.columns || [], + [] + ).then((info) => { setCanVisualize(Boolean(info)); }); }, [dataView, savedSearch.columns, timeField]); @@ -81,7 +89,13 @@ export function DiscoverChart({ if (!timeField) { return; } - triggerVisualizeActions(timeField, savedSearch.columns || [], dataView); + triggerVisualizeActions( + getUiActions(), + timeField, + savedSearch.columns || [], + PLUGIN_ID, + dataView + ); }, [dataView, savedSearch.columns, timeField]); const onShowChartOptions = useCallback(() => { diff --git a/src/plugins/discover/public/application/main/components/sidebar/__stories__/discover_field_visualize.stories.tsx b/src/plugins/discover/public/application/main/components/sidebar/__stories__/discover_field_visualize.stories.tsx deleted file mode 100644 index 397ada8da109..000000000000 --- a/src/plugins/discover/public/application/main/components/sidebar/__stories__/discover_field_visualize.stories.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { storiesOf } from '@storybook/react'; -import React from 'react'; -import { DiscoverFieldVisualizeInner } from '../discover_field_visualize_inner'; -import { numericField as field } from './fields'; - -const visualizeInfo = { - href: 'http://localhost:9001/', - field, -}; - -const handleVisualizeLinkClick = () => { - alert('Clicked'); -}; - -storiesOf('components/sidebar/DiscoverFieldVisualizeInner', module).add('default', () => ( - -)); diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.scss b/src/plugins/discover/public/application/main/components/sidebar/discover_field.scss index 40bc58cef702..3e7a7a4d4ac1 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.scss +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.scss @@ -1,8 +1,3 @@ -.dscSidebarItem__fieldPopoverPanel { - min-width: $euiSizeXXL * 6.5; - max-width: $euiSizeXXL * 7.5; -} - .dscSidebarItem--multi { .kbnFieldButton__button { padding-left: 0; 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 ffb2ec7585ed..caa8539ab6b3 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 @@ -8,7 +8,6 @@ import { act } from 'react-dom/test-utils'; import { EuiPopover, EuiProgress, EuiButtonIcon } from '@elastic/eui'; -import { ReactWrapper } from 'enzyme'; import React from 'react'; import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl } from '@kbn/test-jest-helpers'; @@ -85,6 +84,7 @@ async function getComponent({ getDetails: jest.fn(() => ({ buckets: [], error: '', exists: 1, total: 2 })), ...(onAddFilterExists && { onAddFilter: jest.fn() }), onAddField: jest.fn(), + onEditField: jest.fn(), onRemoveField: jest.fn(), showFieldStats, selected, @@ -140,6 +140,9 @@ async function getComponent({ ); + // wait for lazy modules + await new Promise((resolve) => setTimeout(resolve, 0)); + await comp.update(); return { comp, props }; } @@ -233,29 +236,100 @@ describe('discover sidebar field', function () { aggregatable: true, searchable: true, }); - let comp: ReactWrapper; + + const { comp } = await getComponent({ showFieldStats: true, field, onAddFilterExists: true }); await act(async () => { - const result = await getComponent({ showFieldStats: true, field, onAddFilterExists: true }); - comp = result.comp; + const fieldItem = findTestSubject(comp, 'field-machine.os.raw-showDetails'); + await fieldItem.simulate('click'); await comp.update(); }); + await comp.update(); + + expect(comp.find(EuiPopover).prop('isOpen')).toBe(true); + expect(findTestSubject(comp, 'dscFieldStats-title').text()).toBe('Top values'); + expect(findTestSubject(comp, 'dscFieldStats-topValues-bucket')).toHaveLength(2); + expect( + findTestSubject(comp, 'dscFieldStats-topValues-formattedFieldValue').first().text() + ).toBe('osx'); + expect(comp.find(EuiProgress)).toHaveLength(2); + expect(findTestSubject(comp, 'dscFieldStats-topValues').find(EuiButtonIcon)).toHaveLength(4); + }); + it('should include popover actions', async function () { + const field = new DataViewField({ + name: 'extension.keyword', + type: 'string', + esTypes: ['keyword'], + aggregatable: true, + searchable: true, + }); + + const { comp, props } = await getComponent({ field, onAddFilterExists: true }); + await act(async () => { - const fieldItem = findTestSubject(comp, 'field-machine.os.raw-showDetails'); + const fieldItem = findTestSubject(comp, 'field-extension.keyword-showDetails'); await fieldItem.simulate('click'); await comp.update(); }); - await comp!.update(); + await comp.update(); - expect(comp!.find(EuiPopover).prop('isOpen')).toBe(true); - expect(findTestSubject(comp!, 'dscFieldStats-title').text()).toBe('Top values'); - expect(findTestSubject(comp!, 'dscFieldStats-topValues-bucket')).toHaveLength(2); + expect(comp.find(EuiPopover).prop('isOpen')).toBe(true); expect( - findTestSubject(comp!, 'dscFieldStats-topValues-formattedFieldValue').first().text() - ).toBe('osx'); - expect(comp!.find(EuiProgress)).toHaveLength(2); - expect(findTestSubject(comp!, 'dscFieldStats-topValues').find(EuiButtonIcon)).toHaveLength(4); + comp.find('[data-test-subj="fieldPopoverHeader_addField-extension.keyword"]').exists() + ).toBeTruthy(); + expect( + comp + .find('[data-test-subj="discoverFieldListPanelAddExistFilter-extension.keyword"]') + .exists() + ).toBeTruthy(); + expect( + comp.find('[data-test-subj="discoverFieldListPanelEdit-extension.keyword"]').exists() + ).toBeTruthy(); + expect( + comp.find('[data-test-subj="discoverFieldListPanelDelete-extension.keyword"]').exists() + ).toBeFalsy(); + + await act(async () => { + const fieldItem = findTestSubject(comp, 'fieldPopoverHeader_addField-extension.keyword'); + await fieldItem.simulate('click'); + await comp.update(); + }); + + expect(props.onAddField).toHaveBeenCalledWith('extension.keyword'); + + await comp.update(); + + expect(comp.find(EuiPopover).prop('isOpen')).toBe(false); + }); + + it('should not include + action for selected fields', async function () { + const field = new DataViewField({ + name: 'extension.keyword', + type: 'string', + esTypes: ['keyword'], + aggregatable: true, + searchable: true, + }); + + const { comp } = await getComponent({ + field, + onAddFilterExists: true, + selected: true, + }); + + await act(async () => { + const fieldItem = findTestSubject(comp, 'field-extension.keyword-showDetails'); + await fieldItem.simulate('click'); + await comp.update(); + }); + + await comp.update(); + + expect(comp.find(EuiPopover).prop('isOpen')).toBe(true); + expect( + comp.find('[data-test-subj="fieldPopoverHeader_addField-extension.keyword"]').exists() + ).toBeFalsy(); }); }); 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 10138dec8b4c..591475d949c6 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 @@ -9,32 +9,27 @@ import './discover_field.scss'; import React, { useState, useCallback, memo, useMemo } from 'react'; -import { - EuiPopover, - EuiPopoverTitle, - EuiButtonIcon, - EuiToolTip, - EuiTitle, - EuiIcon, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, -} from '@elastic/eui'; +import { EuiButtonIcon, EuiToolTip, EuiTitle, EuiIcon, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { UiCounterMetricType } from '@kbn/analytics'; import classNames from 'classnames'; import { FieldButton, FieldIcon } from '@kbn/react-field'; import type { DataViewField, DataView } from '@kbn/data-views-plugin/public'; -import { FieldStats } from '@kbn/unified-field-list-plugin/public'; -import { getFieldCapabilities } from '../../../../utils/get_field_capabilities'; +import { + FieldStats, + FieldPopover, + FieldPopoverHeader, + FieldPopoverHeaderProps, + FieldPopoverVisualize, +} from '@kbn/unified-field-list-plugin/public'; import { getTypeForFieldIcon } from '../../../../utils/get_type_for_field_icon'; import { DiscoverFieldDetails } from './discover_field_details'; import { FieldDetails } from './types'; import { getFieldTypeName } from '../../../../utils/get_field_type_name'; -import { DiscoverFieldVisualize } from './discover_field_visualize'; import type { AppState } from '../../services/discover_state'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; -import { SHOW_LEGACY_FIELD_TOP_VALUES } from '../../../../../common'; +import { SHOW_LEGACY_FIELD_TOP_VALUES, PLUGIN_ID } from '../../../../../common'; +import { getUiActions } from '../../../../kibana_services'; function wrapOnDot(str?: string) { // u200B is a non-width white-space character, which allows @@ -93,7 +88,7 @@ interface ActionButtonProps { field: DataViewField; isSelected?: boolean; alwaysShow: boolean; - toggleDisplay: (field: DataViewField) => void; + toggleDisplay: (field: DataViewField, isSelected?: boolean) => void; } const ActionButton: React.FC = memo( @@ -121,7 +116,7 @@ const ActionButton: React.FC = memo( } ev.preventDefault(); ev.stopPropagation(); - toggleDisplay(field); + toggleDisplay(field, isSelected); }} data-test-subj={`fieldToggle-${field.name}`} aria-label={i18n.translate('discover.fieldChooser.discoverField.addButtonAriaLabel', { @@ -149,7 +144,7 @@ const ActionButton: React.FC = memo( } ev.preventDefault(); ev.stopPropagation(); - toggleDisplay(field); + toggleDisplay(field, isSelected); }} data-test-subj={`fieldToggle-${field.name}`} aria-label={i18n.translate( @@ -311,23 +306,48 @@ function DiscoverFieldComponent({ [setOpen, onAddFilter] ); - const toggleDisplay = useCallback( - (f: DataViewField) => { - if (selected) { + const togglePopover = useCallback(() => { + setOpen((value) => !value); + }, [setOpen]); + + const closePopover = useCallback(() => { + setOpen(false); + }, [setOpen]); + + const toggleDisplay: ActionButtonProps['toggleDisplay'] = useCallback( + (f, isCurrentlySelected) => { + closePopover(); + if (isCurrentlySelected) { onRemoveField(f.name); } else { onAddField(f.name); } }, - [onAddField, onRemoveField, selected] + [onAddField, onRemoveField, closePopover] ); - const togglePopover = useCallback(() => { - setOpen(!infoIsOpen); - }, [infoIsOpen]); - const rawMultiFields = useMemo(() => multiFields?.map((f) => f.field), [multiFields]); + const customPopoverHeaderProps: Partial = useMemo( + () => ({ + buttonAddFieldToWorkspaceProps: { + 'aria-label': i18n.translate('discover.fieldChooser.discoverField.addFieldTooltip', { + defaultMessage: 'Add field as column', + }), + }, + buttonAddFilterProps: { + 'data-test-subj': `discoverFieldListPanelAddExistFilter-${field.name}`, + }, + buttonEditFieldProps: { + 'data-test-subj': `discoverFieldListPanelEdit-${field.name}`, + }, + buttonDeleteFieldProps: { + 'data-test-subj': `discoverFieldListPanelDelete-${field.name}`, + }, + }), + [field.name] + ); + if (field.type === '_source') { return ( - - -
{field.displayName}
-
- {onAddFilter && !dataView.metaFields.includes(field.name) && !field.scripted && ( - - - { - setOpen(false); - onAddFilter('_exists_', field.name, '+'); - }} - iconType="filter" - data-test-subj={`discoverFieldListPanelAddExistFilter-${field.name}`} - aria-label={addExistFilterTooltip} - /> - - - )} - {canEditField && ( - - - { - if (onEditField) { - togglePopover(); - onEditField(field.name); - } - }} - iconType="pencil" - data-test-subj={`discoverFieldListPanelEdit-${field.name}`} - aria-label={editFieldTooltip} - /> - - - )} - {canDeleteField && ( - - - { - onDeleteField?.(field.name); - }} - iconType="trash" - data-test-subj={`discoverFieldListPanelDelete-${field.name}`} - color="danger" - aria-label={deleteFieldTooltip} - /> - - - )} -
- - ); - const button = ( } /> ); + if (!isDocumentRecord) { return button; } @@ -512,29 +454,38 @@ function DiscoverFieldComponent({ )} - ); }; + return ( - setOpen(false)} + button={button} + closePopover={closePopover} data-test-subj="discoverFieldListPanelPopover" - anchorPosition="rightUp" - panelClassName="dscSidebarItem__fieldPopoverPanel" - > - {popoverTitle} - {infoIsOpen && renderPopover()} - + renderHeader={() => ( + + )} + renderContent={renderPopover} + /> ); } 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 666b75a68949..3952dd723fc0 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 @@ -85,7 +85,7 @@ describe('discover sidebar', function () { let props: DiscoverSidebarProps; let comp: ReactWrapper; - beforeAll(() => { + beforeAll(async () => { props = getCompProps(); mockDiscoverServices.data.dataViews.getIdsWithTitle = jest .fn() @@ -95,11 +95,14 @@ describe('discover sidebar', function () { return { ...dataView, isPersisted: () => true }; }); - comp = mountWithIntl( + comp = await mountWithIntl( ); + // wait for lazy modules + await new Promise((resolve) => setTimeout(resolve, 0)); + await comp.update(); }); it('should have Selected Fields and Available Fields with Popular Fields sections', function () { @@ -126,8 +129,10 @@ describe('discover sidebar', function () { expect(props.editField).toHaveBeenCalledWith(); }); - it('should render "Edit field" button', () => { + it('should render "Edit field" button', async () => { findTestSubject(comp, 'field-bytes').simulate('click'); + await new Promise((resolve) => setTimeout(resolve, 0)); + await comp.update(); const editFieldButton = findTestSubject(comp, 'discoverFieldListPanelEdit-bytes'); expect(editFieldButton.length).toBe(1); editFieldButton.simulate('click'); 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 355512da5c52..436fdc36f4d3 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 @@ -28,10 +28,11 @@ import { isEqual } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewPicker } from '@kbn/unified-search-plugin/public'; import { DataViewField, getFieldSubtypeMulti } from '@kbn/data-views-plugin/public'; +import { triggerVisualizeActionsTextBasedLanguages } from '@kbn/unified-field-list-plugin/public'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { DiscoverField } from './discover_field'; import { DiscoverFieldSearch } from './discover_field_search'; -import { FIELDS_LIMIT_SETTING } from '../../../../../common'; +import { FIELDS_LIMIT_SETTING, PLUGIN_ID } from '../../../../../common'; import { groupFields } from './lib/group_fields'; import { getDetails } from './lib/get_details'; import { FieldFilterState, getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter'; @@ -40,7 +41,7 @@ import { DiscoverSidebarResponsiveProps } from './discover_sidebar_responsive'; import { VIEW_MODE } from '../../../../components/view_mode_toggle'; import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../../../../components/discover_tour'; import type { DataTableRecord } from '../../../../types'; -import { triggerVisualizeActionsTextBasedLanguages } from './lib/visualize_trigger_utils'; +import { getUiActions } from '../../../../kibana_services'; /** * Default number of available fields displayed and added on scroll @@ -187,7 +188,8 @@ export function DiscoverSidebarComponent({ // In this case the fieldsPerPage needs to be adapted const fieldsRenderedHeight = availableFieldsContainer.current.clientHeight; const avgHeightPerItem = Math.round(fieldsRenderedHeight / fieldsToRender); - const newFieldsPerPage = Math.round(clientHeight / avgHeightPerItem) + 10; + const newFieldsPerPage = + (avgHeightPerItem > 0 ? Math.round(clientHeight / avgHeightPerItem) : 0) + 10; if (newFieldsPerPage >= FIELDS_PER_PAGE && newFieldsPerPage !== fieldsPerPage) { setFieldsPerPage(newFieldsPerPage); setFieldsToRender(newFieldsPerPage); @@ -314,7 +316,13 @@ export function DiscoverSidebarComponent({ const visualizeAggregateQuery = useCallback(() => { const aggregateQuery = state.query && isOfAggregateQueryType(state.query) ? state.query : undefined; - triggerVisualizeActionsTextBasedLanguages(columns, selectedDataView, aggregateQuery); + triggerVisualizeActionsTextBasedLanguages( + getUiActions(), + columns, + PLUGIN_ID, + selectedDataView, + aggregateQuery + ); }, [columns, selectedDataView, state.query]); if (!selectedDataView) { 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 3d0930254475..944355c9a9db 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 @@ -190,7 +190,9 @@ describe('discover responsive sidebar', function () { ); - comp.update(); + // wait for lazy modules + await new Promise((resolve) => setTimeout(resolve, 0)); + await comp.update(); }); }); 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 de3b30810859..274cb85cc353 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 @@ -182,7 +182,8 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) const { dataViewFieldEditor, dataViewEditor } = services; const { availableFields$ } = props; - const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + const canEditDataView = + Boolean(dataViewEditor?.userPermissions.editDataView()) || !selectedDataView?.isPersisted(); useEffect( () => { @@ -241,25 +242,19 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) ] ); - const createNewDataView = useMemo( - () => - canEditDataView - ? () => { - const ref = dataViewEditor.openEditor({ - onSave: async (dataView) => { - onDataViewCreated(dataView); - }, - }); - if (setDataViewEditorRef) { - setDataViewEditorRef(ref); - } - if (closeFlyout) { - closeFlyout(); - } - } - : undefined, - [canEditDataView, dataViewEditor, setDataViewEditorRef, closeFlyout, onDataViewCreated] - ); + const createNewDataView = useCallback(() => { + const ref = dataViewEditor.openEditor({ + onSave: async (dataView) => { + onDataViewCreated(dataView); + }, + }); + if (setDataViewEditorRef) { + setDataViewEditorRef(ref); + } + if (closeFlyout) { + closeFlyout(); + } + }, [dataViewEditor, setDataViewEditorRef, closeFlyout, onDataViewCreated]); if (!selectedDataView) { return null; diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 82f3b1aadadb..66f06169256a 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -68,7 +68,8 @@ export const DiscoverTopNav = ({ const services = useDiscoverServices(); const { dataViewEditor, navigation, dataViewFieldEditor, data, uiSettings, dataViews } = services; - const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + const canEditDataView = + Boolean(dataViewEditor?.userPermissions.editDataView()) || !dataView.isPersisted(); const closeFieldEditor = useRef<() => void | undefined>(); const closeDataViewEditor = useRef<() => void | undefined>(); @@ -124,22 +125,16 @@ export const DiscoverTopNav = ({ [editField, canEditDataView] ); - const createNewDataView = useMemo( - () => - canEditDataView - ? () => { - closeDataViewEditor.current = dataViewEditor.openEditor({ - onSave: async (dataViewToSave) => { - if (dataViewToSave.id) { - onChangeDataView(dataViewToSave.id); - } - }, - allowAdHocDataView: true, - }); - } - : undefined, - [canEditDataView, dataViewEditor, onChangeDataView] - ); + const createNewDataView = useCallback(() => { + closeDataViewEditor.current = dataViewEditor.openEditor({ + onSave: async (dataViewToSave) => { + if (dataViewToSave.id) { + onChangeDataView(dataViewToSave.id); + } + }, + allowAdHocDataView: true, + }); + }, [dataViewEditor, onChangeDataView]); const onCreateDefaultAdHocDataView = useCallback( async (pattern: string) => { diff --git a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx b/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx index 3d7ee2c52e37..1d9eab7e1d8e 100644 --- a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx +++ b/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx @@ -29,7 +29,8 @@ export const buildEditFieldButton = ({ } const { canEdit: canEditField } = getFieldCapabilities(dataView, field); - const canEditDataView = Boolean(services.dataViewEditor?.userPermissions?.editDataView()); + const canEditDataView = + Boolean(services.dataViewEditor?.userPermissions?.editDataView()) || !dataView.isPersisted(); if (!canEditField || !canEditDataView) { return null; diff --git a/src/plugins/event_annotation/common/event_annotation_group/index.ts b/src/plugins/event_annotation/common/event_annotation_group/index.ts index 7cc71e980eef..e088d6878de7 100644 --- a/src/plugins/event_annotation/common/event_annotation_group/index.ts +++ b/src/plugins/event_annotation/common/event_annotation_group/index.ts @@ -60,7 +60,7 @@ export function eventAnnotationGroup(): ExpressionFunctionDefinition< fn: (input, args) => { return { type: 'event_annotation_group', - annotations: args.annotations, + annotations: args.annotations.filter((annotation) => !annotation.isHidden), dataView: args.dataView, }; }, diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.test.ts b/src/plugins/event_annotation/public/event_annotation_service/service.test.ts index 5abed8ba618b..c5df7045fc4d 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.test.ts +++ b/src/plugins/event_annotation/public/event_annotation_service/service.test.ts @@ -19,44 +19,6 @@ describe('Event Annotation Service', () => { expect(eventAnnotationService.toExpression([])).toEqual([]); }); - it('should skip hidden annotations', () => { - expect( - eventAnnotationService.toExpression([ - { - id: 'myEvent', - type: 'manual', - key: { - type: 'point_in_time', - timestamp: '2022', - }, - label: 'Hello', - isHidden: true, - }, - { - id: 'myRangeEvent', - type: 'manual', - key: { - type: 'range', - timestamp: '2021', - endTimestamp: '2022', - }, - label: 'Hello Range', - isHidden: true, - }, - { - id: 'myEvent', - type: 'query', - timeField: '@timestamp', - key: { - type: 'point_in_time', - }, - label: 'Hello Range', - isHidden: true, - filter: { type: 'kibana_query', query: '', language: 'kuery' }, - }, - ]) - ).toEqual([]); - }); it('should process manual point annotations', () => { expect( eventAnnotationService.toExpression([ @@ -79,6 +41,7 @@ describe('Event Annotation Service', () => { function: 'manual_point_event_annotation', arguments: { id: ['myEvent'], + isHidden: [false], time: ['2022'], label: ['Hello'], color: ['#f04e98'], @@ -115,6 +78,7 @@ describe('Event Annotation Service', () => { function: 'manual_range_event_annotation', arguments: { id: ['myEvent'], + isHidden: [false], time: ['2021'], endTime: ['2022'], label: ['Hello'], @@ -149,6 +113,7 @@ describe('Event Annotation Service', () => { function: 'query_point_event_annotation', arguments: { id: ['myEvent'], + isHidden: [false], timeField: ['@timestamp'], label: ['Hello'], color: ['#f04e98'], @@ -221,6 +186,7 @@ describe('Event Annotation Service', () => { function: 'manual_point_event_annotation', arguments: { id: ['myEvent'], + isHidden: [false], time: ['2022'], label: ['Hello'], color: ['#f04e98'], @@ -240,6 +206,7 @@ describe('Event Annotation Service', () => { function: 'manual_range_event_annotation', arguments: { id: ['myRangeEvent'], + isHidden: [false], time: ['2021'], endTime: ['2022'], label: ['Hello Range'], @@ -257,6 +224,7 @@ describe('Event Annotation Service', () => { function: 'query_point_event_annotation', arguments: { id: ['myEvent'], + isHidden: [false], timeField: ['@timestamp'], label: ['Hello'], color: ['#f04e98'], @@ -320,6 +288,7 @@ describe('Event Annotation Service', () => { function: 'query_point_event_annotation', arguments: { id: ['myEvent'], + isHidden: [false], timeField: ['@timestamp'], label: ['Hello'], color: ['#f04e98'], diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index bb8c8d0d62eb..3722c4d3557b 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -25,9 +25,8 @@ export function hasIcon(icon: string | undefined): icon is string { export function getEventAnnotationService(): EventAnnotationServiceType { const annotationsToExpression = (annotations: EventAnnotationConfig[]) => { - const visibleAnnotations = annotations.filter(({ isHidden }) => !isHidden); const [queryBasedAnnotations, manualBasedAnnotations] = partition( - visibleAnnotations, + annotations, isQueryAnnotationConfig ); @@ -50,6 +49,7 @@ export function getEventAnnotationService(): EventAnnotationServiceType { label: [label || defaultAnnotationLabel], color: [color || defaultAnnotationRangeColor], outside: [Boolean(outside)], + isHidden: [Boolean(annotation.isHidden)], }, }, ], @@ -71,6 +71,7 @@ export function getEventAnnotationService(): EventAnnotationServiceType { lineStyle: [lineStyle || 'solid'], icon: hasIcon(icon) ? [icon] : ['triangle'], textVisibility: [textVisibility || false], + isHidden: [Boolean(annotation.isHidden)], }, }, ], @@ -112,6 +113,7 @@ export function getEventAnnotationService(): EventAnnotationServiceType { filter: filter ? [queryToAst(filter)] : [], extraFields: extraFields || [], ignoreGlobalFilters: [Boolean(ignoreGlobalFilters)], + isHidden: [Boolean(annotation.isHidden)], }, }, ], diff --git a/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx index 61f45ce84e3c..1d479bd9b4c1 100644 --- a/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx @@ -57,7 +57,9 @@ export function ReactExpressionRenderer({ return (
{isEmpty && } - {isLoading && } + {isLoading && ( + + )} {!isLoading && error && renderError?.(error.message, error)}
diff --git a/src/plugins/guided_onboarding/common/types.ts b/src/plugins/guided_onboarding/common/types.ts index 412154ede98b..92cc3af1ff1d 100644 --- a/src/plugins/guided_onboarding/common/types.ts +++ b/src/plugins/guided_onboarding/common/types.ts @@ -9,7 +9,7 @@ export type GuideId = 'observability' | 'security' | 'search'; export type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability'; -export type SecurityStepIds = 'add_data' | 'rules' | 'alerts' | 'cases'; +export type SecurityStepIds = 'add_data' | 'rules' | 'alertsCases'; export type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience'; export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 3506c15fcba3..5bd846aebbde 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -228,5 +228,54 @@ describe('Guided setup', () => { expect(find('activeStepButtonLabel').text()).toEqual('Continue'); }); }); + + describe('Quit guide modal', () => { + beforeEach(async () => { + const { component, find, exists } = testBed; + + await act(async () => { + // Enable the "search" guide + await apiService.updateGuideState(mockActiveSearchGuideState, true); + }); + + component.update(); + + await act(async () => { + find('quitGuideButton').simulate('click'); + }); + + component.update(); + + expect(exists('quitGuideModal')).toBe(true); + }); + + test('quit a guide', async () => { + const { component, find, exists } = testBed; + + await act(async () => { + find('confirmModalConfirmButton').simulate('click'); + }); + + component.update(); + + expect(exists('quitGuideModal')).toBe(false); + // For now, the guide button is disabled once a user quits a guide + // This behavior will change once https://github.com/elastic/kibana/issues/141129 is implemented + expect(exists('disabledGuideButton')).toBe(true); + }); + + test('cancels out of the quit guide confirmation modal', async () => { + const { component, find, exists } = testBed; + + await act(async () => { + find('confirmModalCancelButton').simulate('click'); + }); + + component.update(); + + expect(exists('quitGuideModal')).toBe(false); + expect(exists('guideButton')).toBe(true); + }); + }); }); }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index bf57d502918d..7c122492d84a 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -29,6 +29,7 @@ import { 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 { GuideState, GuideStepIds } from '../../common/types'; import type { GuideConfig, StepConfig } from '../types'; @@ -36,6 +37,7 @@ import type { GuideConfig, StepConfig } from '../types'; import type { ApiService } from '../services/api'; import { GuideStep } from './guide_panel_step'; +import { QuitGuideModal } from './quit_guide_modal'; import { getGuidePanelStyles } from './guide_panel.styles'; interface GuidePanelProps { @@ -84,6 +86,7 @@ const getProgress = (state?: GuideState): number => { export const GuidePanel = ({ api, application }: GuidePanelProps) => { const { euiTheme } = useEuiTheme(); const [isGuideOpen, setIsGuideOpen] = useState(false); + const [isQuitGuideModalOpen, setIsQuitGuideModalOpen] = useState(false); const [guideState, setGuideState] = useState(undefined); const styles = getGuidePanelStyles(euiTheme); @@ -108,11 +111,20 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { await api.completeGuide(guideState!.guideId); }; + const openQuitGuideModal = () => { + // Close the dropdown panel + setIsGuideOpen(false); + // Open the confirmation modal + setIsQuitGuideModalOpen(true); + }; + + const closeQuitGuideModal = () => { + setIsQuitGuideModalOpen(false); + }; + useEffect(() => { const subscription = api.fetchActiveGuideState$().subscribe((newGuideState) => { - if (newGuideState) { - setGuideState(newGuideState); - } + setGuideState(newGuideState); }); return () => subscription.unsubscribe(); }, [api]); @@ -236,7 +248,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { - {guideConfig?.steps.map((step, index, steps) => { + {guideConfig?.steps.map((step, index) => { const accordionId = htmlIdGenerator(`accordion${index}`)(); const stepState = guideState?.steps[index]; @@ -271,10 +283,9 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { - {/* TODO: Implement exit guide modal - https://github.com/elastic/kibana/issues/139804 */} - {}}> + {i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', { - defaultMessage: 'Exit setup guide', + defaultMessage: 'Quit setup guide', })} @@ -325,6 +336,10 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { )} + + {isQuitGuideModalOpen && ( + + )} ); }; 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 index 498059564e6e..8d34d45b7a53 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts @@ -8,18 +8,25 @@ import { EuiThemeComputed } from '@elastic/eui'; import { css } from '@emotion/react'; +import { StepStatus } from '../../common/types'; -export const getGuidePanelStepStyles = (euiTheme: EuiThemeComputed) => ({ +export const getGuidePanelStepStyles = (euiTheme: EuiThemeComputed, stepStatus: StepStatus) => ({ stepNumber: css` width: 24px; height: 24px; border-radius: 50%; - border: 2px solid ${euiTheme.colors.success}; + border: 2px solid + ${stepStatus === 'inactive' ? euiTheme.colors.lightShade : euiTheme.colors.success}; font-weight: ${euiTheme.font.weight.medium}; text-align: center; line-height: 1.4; + color: ${stepStatus === 'inactive' ? euiTheme.colors.subduedText : euiTheme.colors.text}; `, stepTitle: css` - font-weight: ${euiTheme.font.weight.bold}; + font-weight: ${euiTheme.font.weight.semiBold}; + color: ${stepStatus === 'inactive' ? euiTheme.colors.subduedText : euiTheme.colors.text}; + .euiAccordion-isOpen & { + color: ${euiTheme.colors.title}; + } `, }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx index 8a98d87debf1..c05ad6ec310c 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx @@ -40,10 +40,10 @@ export const GuideStep = ({ navigateToStep, }: GuideStepProps) => { const { euiTheme } = useEuiTheme(); - const styles = getGuidePanelStepStyles(euiTheme); + const styles = getGuidePanelStepStyles(euiTheme, stepStatus); - const buttonContent = ( - + const stepTitleContent = ( + {stepStatus === 'complete' ? ( @@ -61,45 +61,49 @@ export const GuideStep = ({ return (
- - <> - + {stepStatus === 'complete' ? ( + <>{stepTitleContent} + ) : ( + + <> + - -
    - {stepConfig.descriptionList.map((description, index) => { - return
  • {description}
  • ; - })} -
-
+ +
    + {stepConfig.descriptionList.map((description, index) => { + return
  • {description}
  • ; + })} +
+
- - {(stepStatus === 'in_progress' || stepStatus === 'active') && ( - - - navigateToStep(stepConfig.id, stepConfig.location)} - fill - data-test-subj="activeStepButtonLabel" - > - {stepStatus === 'active' - ? i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { - defaultMessage: 'Start', - }) - : i18n.translate('guidedOnboarding.dropdownPanel.continueStepButtonLabel', { - defaultMessage: 'Continue', - })} - - - - )} - -
+ + {(stepStatus === 'in_progress' || stepStatus === 'active') && ( + + + navigateToStep(stepConfig.id, stepConfig.location)} + fill + data-test-subj="activeStepButtonLabel" + > + {stepStatus === 'active' + ? i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { + defaultMessage: 'Start', + }) + : i18n.translate('guidedOnboarding.dropdownPanel.continueStepButtonLabel', { + defaultMessage: 'Continue', + })} + + + + )} + +
+ )}
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx new file mode 100644 index 000000000000..a7a7e34c311b --- /dev/null +++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { useState } from 'react'; + +import { EuiText, EuiConfirmModal } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { GuideState } from '../../common/types'; +import { apiService } from '../services/api'; + +interface QuitGuideModalProps { + closeModal: () => void; + currentGuide: GuideState; +} + +export const QuitGuideModal = ({ closeModal, currentGuide }: QuitGuideModalProps) => { + const [isDeleting, setIsDeleting] = useState(false); + + const deleteGuide = async () => { + setIsDeleting(true); + await apiService.deactivateGuide(currentGuide); + closeModal(); + }; + + return ( + + +

+ {i18n.translate('guidedOnboarding.quitGuideModal.modalDescription', { + defaultMessage: + 'You can restart anytime by opening the Setup guide from the Help menu.', + })} +

+
+
+ ); +}; diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/security.ts b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts index df17d00d7f2d..8eafa3b51c40 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/security.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts @@ -21,6 +21,11 @@ export const securityConfig: GuideConfig = { '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.', ], + integration: 'endpoint', + location: { + appID: 'integrations', + path: '/browse/security', + }, }, { id: 'rules', @@ -32,17 +37,8 @@ export const securityConfig: GuideConfig = { ], }, { - id: 'alerts', - title: 'View Alerts', - 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', + id: 'alertsCases', + title: 'Alerts and cases', descriptionList: [ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', diff --git a/src/plugins/guided_onboarding/public/mocks.tsx b/src/plugins/guided_onboarding/public/mocks.tsx new file mode 100644 index 000000000000..dcac2cfc1c0f --- /dev/null +++ b/src/plugins/guided_onboarding/public/mocks.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BehaviorSubject } from 'rxjs'; +import { GuidedOnboardingPluginStart } from '.'; + +const apiServiceMock: jest.Mocked = { + guidedOnboardingApi: { + setup: jest.fn(), + fetchActiveGuideState$: () => new BehaviorSubject(undefined), + fetchAllGuidesState: jest.fn(), + updateGuideState: jest.fn(), + activateGuide: jest.fn(), + completeGuide: jest.fn(), + isGuideStepActive$: () => new BehaviorSubject(false), + startGuideStep: jest.fn(), + completeGuideStep: jest.fn(), + isGuidedOnboardingActiveForIntegration$: () => new BehaviorSubject(false), + completeGuidedOnboardingForIntegration: jest.fn(), + isGuidePanelOpen$: new BehaviorSubject(false), + }, +}; + +export const guidedOnboardingMock = { + createSetup: () => {}, + createStart: () => apiServiceMock, +}; diff --git a/src/plugins/guided_onboarding/public/services/api.mocks.ts b/src/plugins/guided_onboarding/public/services/api.mocks.ts new file mode 100644 index 000000000000..19dd67c7d7b1 --- /dev/null +++ b/src/plugins/guided_onboarding/public/services/api.mocks.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 { GuideState } from '../../common/types'; + +export const searchAddDataActiveState: GuideState = { + guideId: 'search', + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'active', + }, + { + id: 'browse_docs', + status: 'inactive', + }, + { + id: 'search_experience', + status: 'inactive', + }, + ], +}; + +export const searchAddDataInProgressState: GuideState = { + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'in_progress', + }, + { + id: 'browse_docs', + status: 'inactive', + }, + { + id: 'search_experience', + status: 'inactive', + }, + ], + guideId: 'search', +}; + +export const securityAddDataInProgressState: GuideState = { + guideId: 'security', + status: 'in_progress', + isActive: true, + steps: [ + { + id: 'add_data', + status: 'in_progress', + }, + { + id: 'rules', + status: 'inactive', + }, + ], +}; + +export const securityRulesActivesState: GuideState = { + guideId: 'security', + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'rules', + status: 'active', + }, + ], +}; + +export const noGuideActiveState: GuideState = { + guideId: 'security', + status: 'in_progress', + isActive: false, + steps: [ + { + id: 'add_data', + status: 'in_progress', + }, + { + id: 'rules', + status: 'inactive', + }, + ], +}; diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index ffe5596bd7e3..5deb3d50987f 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -14,29 +14,17 @@ import { API_BASE_PATH } from '../../common/constants'; import { guidesConfig } from '../constants/guides_config'; import type { GuideState } from '../../common/types'; import { ApiService } from './api'; +import { + noGuideActiveState, + searchAddDataActiveState, + securityAddDataInProgressState, + securityRulesActivesState, +} from './api.mocks'; const searchGuide = 'search'; const firstStep = guidesConfig[searchGuide].steps[0].id; - -const mockActiveSearchGuideState: GuideState = { - guideId: searchGuide, - isActive: true, - status: 'in_progress', - steps: [ - { - id: 'add_data', - status: 'active', - }, - { - id: 'browse_docs', - status: 'inactive', - }, - { - id: 'search_experience', - status: 'inactive', - }, - ], -}; +const endpointIntegration = 'endpoint'; +const kubernetesIntegration = 'kubernetes'; describe('GuidedOnboarding ApiService', () => { let httpClient: jest.Mocked; @@ -46,7 +34,7 @@ describe('GuidedOnboarding ApiService', () => { beforeEach(() => { httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); httpClient.get.mockResolvedValue({ - state: { activeGuide: searchGuide, activeStep: firstStep }, + state: [securityAddDataInProgressState], }); apiService = new ApiService(); apiService.setup(httpClient); @@ -72,7 +60,7 @@ describe('GuidedOnboarding ApiService', () => { await apiService.activateGuide(searchGuide); const state = await firstValueFrom(apiService.fetchActiveGuideState$()); - expect(state).toEqual(mockActiveSearchGuideState); + expect(state).toEqual(searchAddDataActiveState); }); }); @@ -84,17 +72,31 @@ describe('GuidedOnboarding ApiService', () => { }); }); + describe('deactivateGuide', () => { + it('deactivates an existing guide', async () => { + await apiService.deactivateGuide(searchAddDataActiveState); + + expect(httpClient.put).toHaveBeenCalledTimes(1); + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + ...searchAddDataActiveState, + isActive: false, + }), + }); + }); + }); + describe('updateGuideState', () => { it('sends a request to the put API', async () => { const updatedState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'in_progress', // update the first step status }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[1], + searchAddDataActiveState.steps[2], ], }; await apiService.updateGuideState(updatedState, false); @@ -108,14 +110,14 @@ describe('GuidedOnboarding ApiService', () => { describe('isGuideStepActive$', () => { it('returns true if the step has been started', async (done) => { const updatedState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'in_progress', }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[1], + searchAddDataActiveState.steps[2], ], }; await apiService.updateGuideState(updatedState, false); @@ -130,7 +132,7 @@ describe('GuidedOnboarding ApiService', () => { }); it('returns false if the step is not been started', async (done) => { - await apiService.updateGuideState(mockActiveSearchGuideState, false); + await apiService.updateGuideState(searchAddDataActiveState, false); subscription = apiService .isGuideStepActive$(searchGuide, firstStep) .subscribe((isStepActive) => { @@ -170,21 +172,18 @@ describe('GuidedOnboarding ApiService', () => { }); it('reactivates a guide that has already been started', async () => { - await apiService.activateGuide(searchGuide, mockActiveSearchGuideState); + await apiService.activateGuide(searchGuide, searchAddDataActiveState); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify({ - ...mockActiveSearchGuideState, - isActive: true, - }), + body: JSON.stringify(searchAddDataActiveState), }); }); }); describe('completeGuide', () => { const readyToCompleteGuideState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { id: 'add_data', @@ -224,7 +223,7 @@ describe('GuidedOnboarding ApiService', () => { it('returns undefined if the selected guide has uncompleted steps', async () => { const incompleteGuideState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { id: 'add_data', @@ -249,7 +248,7 @@ describe('GuidedOnboarding ApiService', () => { describe('startGuideStep', () => { beforeEach(async () => { - await apiService.updateGuideState(mockActiveSearchGuideState, false); + await apiService.updateGuideState(searchAddDataActiveState, false); }); it('updates the selected step and marks it as in_progress', async () => { @@ -257,16 +256,16 @@ describe('GuidedOnboarding ApiService', () => { expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, isActive: true, status: 'in_progress', steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'in_progress', }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[1], + searchAddDataActiveState.steps[2], ], }), }); @@ -281,14 +280,14 @@ describe('GuidedOnboarding ApiService', () => { describe('completeGuideStep', () => { it(`completes the step when it's in progress`, async () => { const updatedState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'in_progress', // Mark a step as in_progress in order to test the "completeGuideStep" behavior }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[1], + searchAddDataActiveState.steps[2], ], }; await apiService.updateGuideState(updatedState, false); @@ -303,14 +302,14 @@ describe('GuidedOnboarding ApiService', () => { ...updatedState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'complete', }, { - id: mockActiveSearchGuideState.steps[1].id, + id: searchAddDataActiveState.steps[1].id, status: 'active', }, - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[2], ], }), }); @@ -322,11 +321,91 @@ describe('GuidedOnboarding ApiService', () => { }); it('does nothing if the step is not in progress', async () => { - await apiService.updateGuideState(mockActiveSearchGuideState, false); + await apiService.updateGuideState(searchAddDataActiveState, false); await apiService.completeGuideStep(searchGuide, firstStep); // Expect only 1 call from updateGuideState() expect(httpClient.put).toHaveBeenCalledTimes(1); }); }); + + describe('isGuidedOnboardingActiveForIntegration$', () => { + it('returns true if the integration is part of the active step', async (done) => { + httpClient.get.mockResolvedValue({ + state: [securityAddDataInProgressState], + }); + apiService.setup(httpClient); + subscription = apiService + .isGuidedOnboardingActiveForIntegration$(endpointIntegration) + .subscribe((isIntegrationInGuideStep) => { + if (isIntegrationInGuideStep) { + done(); + } + }); + }); + + it('returns false if another integration is part of the active step', async (done) => { + httpClient.get.mockResolvedValue({ + state: [securityAddDataInProgressState], + }); + apiService.setup(httpClient); + subscription = apiService + .isGuidedOnboardingActiveForIntegration$(kubernetesIntegration) + .subscribe((isIntegrationInGuideStep) => { + if (!isIntegrationInGuideStep) { + done(); + } + }); + }); + + it('returns false if no guide is active', async (done) => { + httpClient.get.mockResolvedValue({ + state: [noGuideActiveState], + }); + apiService.setup(httpClient); + subscription = apiService + .isGuidedOnboardingActiveForIntegration$(endpointIntegration) + .subscribe((isIntegrationInGuideStep) => { + if (!isIntegrationInGuideStep) { + done(); + } + }); + }); + }); + + describe('completeGuidedOnboardingForIntegration', () => { + it(`completes the step if it's active for the integration`, async () => { + httpClient.get.mockResolvedValue({ + state: [securityAddDataInProgressState], + }); + apiService.setup(httpClient); + + await apiService.completeGuidedOnboardingForIntegration(endpointIntegration); + expect(httpClient.put).toHaveBeenCalledTimes(1); + // this assertion depends on the guides config + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify(securityRulesActivesState), + }); + }); + + it(`does nothing if the step has a different integration`, async () => { + httpClient.get.mockResolvedValue({ + state: [securityAddDataInProgressState], + }); + apiService.setup(httpClient); + + await apiService.completeGuidedOnboardingForIntegration(kubernetesIntegration); + expect(httpClient.put).not.toHaveBeenCalled(); + }); + + it(`does nothing if no guide is active`, async () => { + httpClient.get.mockResolvedValue({ + state: [noGuideActiveState], + }); + apiService.setup(httpClient); + + await apiService.completeGuidedOnboardingForIntegration(endpointIntegration); + 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 1adfaa5d8cc2..7c970717be5f 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -9,11 +9,17 @@ import { HttpSetup } from '@kbn/core/public'; import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; +import { GuidedOnboardingApi } from '../types'; +import { + getGuideConfig, + getInProgressStepId, + isIntegrationInGuideStep, + isLastStep, +} from './helpers'; import { API_BASE_PATH } from '../../common/constants'; import type { GuideState, GuideId, GuideStep, GuideStepIds } from '../../common/types'; -import { isLastStep, getGuideConfig } from './helpers'; -export class ApiService { +export class ApiService implements GuidedOnboardingApi { private client: HttpSetup | undefined; private onboardingGuideState$!: BehaviorSubject; public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false); @@ -89,7 +95,8 @@ export class ApiService { const response = await this.client.put<{ state: GuideState }>(`${API_BASE_PATH}/state`, { body: JSON.stringify(newState), }); - this.onboardingGuideState$.next(newState); + // If the guide has been deactivated, we return undefined + this.onboardingGuideState$.next(newState.isActive ? newState : undefined); this.isGuidePanelOpen$.next(panelState); return response; } catch (error) { @@ -102,8 +109,8 @@ export class ApiService { /** * Activates a guide by guideId * This is useful for the onboarding landing page, when a user selects a guide to start or continue - * @param {GuideId} guideID the id of the guide (one of search, observability, security) - * @param {GuideState} guideState (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide) + * @param {GuideId} guideId the id of the guide (one of search, observability, security) + * @param {GuideState} guide (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide) * @return {Promise} a promise with the updated guide state */ public async activateGuide( @@ -146,11 +153,27 @@ export class ApiService { } } + /** + * Marks a guide as inactive + * This is useful for the dropdown panel, when a user quits a guide + * @param {GuideState} guide (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide) + * @return {Promise} a promise with the updated guide state + */ + public async deactivateGuide(guide: GuideState): Promise<{ state: GuideState } | undefined> { + return await this.updateGuideState( + { + ...guide, + isActive: false, + }, + false + ); + } + /** * Completes a guide * Updates the overall guide status to 'complete', and marks it as inactive * This is useful for the dropdown panel, when the user clicks the "Continue using Elastic" button after completing all steps - * @param {GuideId} guideID the id of the guide (one of search, observability, security) + * @param {GuideId} guideId the id of the guide (one of search, observability, security) * @return {Promise} a promise with the updated guide state */ public async completeGuide(guideId: GuideId): Promise<{ state: GuideState } | undefined> { @@ -300,6 +323,38 @@ export class ApiService { return undefined; } + + /** + * An observable with the boolean value if the guided onboarding is currently active for the integration. + * Returns true, if the passed integration is used in the current guide's step. + * Returns false otherwise. + * @param {string} integration the integration (package name) to check for in the guided onboarding config + * @return {Observable} an observable with the boolean value + */ + public isGuidedOnboardingActiveForIntegration$(integration?: string): Observable { + return this.fetchActiveGuideState$().pipe( + map((state) => { + return state ? isIntegrationInGuideStep(state, integration) : false; + }) + ); + } + + public async completeGuidedOnboardingForIntegration( + integration?: string + ): Promise<{ state: GuideState } | undefined> { + if (integration) { + const currentState = await firstValueFrom(this.fetchActiveGuideState$()); + if (currentState) { + const inProgressStepId = getInProgressStepId(currentState); + if (inProgressStepId) { + const isIntegrationStepActive = isIntegrationInGuideStep(currentState, integration); + if (isIntegrationStepActive) { + return await this.completeGuideStep(currentState?.guideId, inProgressStepId); + } + } + } + } + } } 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 index bc09a9185424..586566fe9488 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.test.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.test.ts @@ -7,11 +7,16 @@ */ import { guidesConfig } from '../constants/guides_config'; -import { isLastStep } from './helpers'; +import { isIntegrationInGuideStep, isLastStep } from './helpers'; +import { + noGuideActiveState, + securityAddDataInProgressState, + securityRulesActivesState, +} from './api.mocks'; const searchGuide = 'search'; const firstStep = guidesConfig[searchGuide].steps[0].id; -const lastStep = guidesConfig[searchGuide].steps[2].id; +const lastStep = guidesConfig[searchGuide].steps[guidesConfig[searchGuide].steps.length - 1].id; describe('GuidedOnboarding ApiService helpers', () => { // this test suite depends on the guides config @@ -26,4 +31,27 @@ describe('GuidedOnboarding ApiService helpers', () => { expect(result).toBe(false); }); }); + + describe('isIntegrationInGuideStep', () => { + it('return true if the integration is defined in the guide step config', () => { + const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'endpoint'); + expect(result).toBe(true); + }); + it('returns false if a different integration is defined in the guide step', () => { + const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'kubernetes'); + expect(result).toBe(false); + }); + it('returns false if no integration is defined in the guide step', () => { + const result = isIntegrationInGuideStep(securityRulesActivesState, 'endpoint'); + expect(result).toBe(false); + }); + it('returns false if no guide is active', () => { + const result = isIntegrationInGuideStep(noGuideActiveState, 'endpoint'); + expect(result).toBe(false); + }); + it('returns false if no integration passed', () => { + const result = isIntegrationInGuideStep(securityAddDataInProgressState); + expect(result).toBe(false); + }); + }); }); diff --git a/src/plugins/guided_onboarding/public/services/helpers.ts b/src/plugins/guided_onboarding/public/services/helpers.ts index ea4245be9915..0e738646c558 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { GuideId } from '../../common/types'; +import type { GuideId, GuideState, GuideStepIds } from '../../common/types'; import { guidesConfig } from '../constants/guides_config'; -import type { GuideConfig, StepConfig } from '../types'; +import { GuideConfig, StepConfig } from '../types'; export const getGuideConfig = (guideID?: string): GuideConfig | undefined => { if (guideID && Object.keys(guidesConfig).includes(guideID)) { @@ -33,3 +33,26 @@ export const isLastStep = (guideID: string, stepID: string): boolean => { } return false; }; + +export const getInProgressStepId = (state: GuideState): GuideStepIds | undefined => { + const inProgressStep = state.steps.find((step) => step.status === 'in_progress'); + return inProgressStep ? inProgressStep.id : undefined; +}; + +const getInProgressStepConfig = (state: GuideState): StepConfig | undefined => { + const inProgressStepId = getInProgressStepId(state); + if (inProgressStepId) { + const config = getGuideConfig(state.guideId); + if (config) { + return config.steps.find((step) => step.id === inProgressStepId); + } + } +}; + +export const isIntegrationInGuideStep = (state: GuideState, integration?: string): boolean => { + if (state.isActive) { + const stepConfig = getInProgressStepConfig(state); + return stepConfig ? stepConfig.integration === integration : false; + } + return false; +}; diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 4a16c16336c6..ba7271756cbb 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -6,15 +6,16 @@ * Side Public License, v 1. */ +import { Observable } from 'rxjs'; import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; -import { GuideId, GuideStepIds, StepStatus } from '../common/types'; -import { ApiService } from './services/api'; +import { HttpSetup } from '@kbn/core/public'; +import { GuideId, GuideState, GuideStepIds, StepStatus } from '../common/types'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface GuidedOnboardingPluginSetup {} export interface GuidedOnboardingPluginStart { - guidedOnboardingApi?: ApiService; + guidedOnboardingApi?: GuidedOnboardingApi; } export interface AppPluginStartDependencies { @@ -25,6 +26,35 @@ export interface ClientConfigType { ui: boolean; } +export interface GuidedOnboardingApi { + setup: (httpClient: HttpSetup) => void; + fetchActiveGuideState$: () => Observable; + fetchAllGuidesState: () => Promise<{ state: GuideState[] } | undefined>; + updateGuideState: ( + newState: GuideState, + panelState: boolean + ) => Promise<{ state: GuideState } | undefined>; + activateGuide: ( + guideId: GuideId, + guide?: GuideState + ) => Promise<{ state: GuideState } | undefined>; + completeGuide: (guideId: GuideId) => Promise<{ state: GuideState } | undefined>; + isGuideStepActive$: (guideId: GuideId, stepId: GuideStepIds) => Observable; + startGuideStep: ( + guideId: GuideId, + stepId: GuideStepIds + ) => Promise<{ state: GuideState } | undefined>; + completeGuideStep: ( + guideId: GuideId, + stepId: GuideStepIds + ) => Promise<{ state: GuideState } | undefined>; + isGuidedOnboardingActiveForIntegration$: (integration?: string) => Observable; + completeGuidedOnboardingForIntegration: ( + integration?: string + ) => Promise<{ state: GuideState } | undefined>; + isGuidePanelOpen$: Observable; +} + export interface StepConfig { id: GuideStepIds; title: string; @@ -34,6 +64,7 @@ export interface StepConfig { path: string; }; status?: StepStatus; + integration?: string; } export interface GuideConfig { title: string; diff --git a/src/plugins/guided_onboarding/server/routes/index.ts b/src/plugins/guided_onboarding/server/routes/index.ts index cce5aad08b1e..adc65d0bf686 100755 --- a/src/plugins/guided_onboarding/server/routes/index.ts +++ b/src/plugins/guided_onboarding/server/routes/index.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import type { IRouter, SavedObjectsClient } from '@kbn/core/server'; +import { API_BASE_PATH } from '../../common/constants'; import type { GuideState } from '../../common/types'; import { guidedSetupSavedObjectsType } from '../saved_objects'; @@ -35,7 +36,7 @@ export function defineRoutes(router: IRouter) { // Fetch all guides state; optionally pass the query param ?active=true to only return the active guide router.get( { - path: '/api/guided_onboarding/state', + path: `${API_BASE_PATH}/state`, validate: { query: schema.object({ active: schema.maybe(schema.boolean()), @@ -69,7 +70,7 @@ export function defineRoutes(router: IRouter) { // will also check any existing active guides and update them to an "inactive" state router.put( { - path: '/api/guided_onboarding/state', + path: `${API_BASE_PATH}/state`, validate: { body: schema.object({ status: schema.string(), diff --git a/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx index df2b3d5c88b9..5ab50ba33a51 100644 --- a/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx +++ b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx @@ -132,7 +132,7 @@ export const RequestCodeViewer = ({ indexPattern, json }: RequestCodeViewerProps )}
- + { }; }; +const tick = (ms: number = 1) => new Promise((r) => setTimeout(r, ms)); + +const until = async (check: () => Promise, pollInterval: number = 1) => { + do { + if (await check()) return; + await tick(pollInterval); + } while (true); +}; + describe('ServerShortUrlClient', () => { describe('.create()', () => { test('can create a short URL', async () => { @@ -72,6 +81,20 @@ describe('ServerShortUrlClient', () => { }, }); }); + + test('initializes "accessDate" and "accessCount" fields on URL creation', async () => { + const { client, locator } = setup(); + const { data } = await client.create({ + locator, + slug: 'lala', + params: { + url: '/app/test#foo/bar/baz', + }, + }); + + expect(data.accessDate).toBeGreaterThan(Date.now() - 1000000); + expect(data.accessCount).toBe(0); + }); }); describe('.resolve()', () => { @@ -85,7 +108,7 @@ describe('ServerShortUrlClient', () => { }); const shortUrl2 = await client.resolve(shortUrl1.data.slug); - expect(shortUrl2.data).toMatchObject(shortUrl1.data); + expect(shortUrl2.data).toStrictEqual(shortUrl1.data); }); test('can create short URL with custom slug', async () => { @@ -128,6 +151,33 @@ describe('ServerShortUrlClient', () => { }) ).rejects.toThrowError(new UrlServiceError(`Slug "lala" already exists.`, 'SLUG_EXISTS')); }); + + test('updates "accessCount" and "accessDate" on URL resolution by slug', async () => { + const { client, locator } = setup(); + const shortUrl1 = await client.create({ + locator, + params: { + url: '/app/test#foo/bar/baz', + }, + }); + + expect(shortUrl1.data.accessDate).toBeGreaterThan(Date.now() - 1000000); + expect(shortUrl1.data.accessCount).toBe(0); + + await client.resolve(shortUrl1.data.slug); + await until(async () => (await client.get(shortUrl1.data.id)).data.accessCount === 1); + const shortUrl2 = await client.get(shortUrl1.data.id); + + expect(shortUrl2.data.accessDate).toBeGreaterThanOrEqual(shortUrl1.data.accessDate); + expect(shortUrl2.data.accessCount).toBe(1); + + await client.resolve(shortUrl1.data.slug); + await until(async () => (await client.get(shortUrl1.data.id)).data.accessCount === 2); + const shortUrl3 = await client.get(shortUrl1.data.id); + + expect(shortUrl3.data.accessDate).toBeGreaterThanOrEqual(shortUrl2.data.accessDate); + expect(shortUrl3.data.accessCount).toBe(2); + }); }); describe('.get()', () => { @@ -141,7 +191,7 @@ describe('ServerShortUrlClient', () => { }); const shortUrl2 = await client.get(shortUrl1.data.id); - expect(shortUrl2.data).toMatchObject(shortUrl1.data); + expect(shortUrl2.data).toStrictEqual(shortUrl1.data); }); test('throws when fetching non-existing short URL', async () => { diff --git a/src/plugins/share/server/url_service/short_urls/short_url_client.ts b/src/plugins/share/server/url_service/short_urls/short_url_client.ts index cecc4c312713..096b2610b916 100644 --- a/src/plugins/share/server/url_service/short_urls/short_url_client.ts +++ b/src/plugins/share/server/url_service/short_urls/short_url_client.ts @@ -143,12 +143,29 @@ export class ServerShortUrlClient implements IShortUrlClient { const { storage } = this.dependencies; const record = await storage.getBySlug(slug); const data = this.injectReferences(record); + this.updateAccessFields(record); return { data, }; } + /** + * Access field updates are executed in the background as we don't need to + * wait for them and confirm that they were successful. + */ + protected updateAccessFields(record: ShortUrlRecord) { + const { storage } = this.dependencies; + const { id, ...attributes } = record.data; + storage + .update(id, { + ...attributes, + accessDate: Date.now(), + accessCount: (attributes.accessCount || 0) + 1, + }) + .catch(() => {}); // We are not interested if it succeeds or not. + } + public async delete(id: string): Promise { const { storage } = this.dependencies; await storage.delete(id); diff --git a/src/plugins/unified_field_list/README.md b/src/plugins/unified_field_list/README.md index 4f0e841de03b..9030a32a3bdc 100755 --- a/src/plugins/unified_field_list/README.md +++ b/src/plugins/unified_field_list/README.md @@ -6,7 +6,52 @@ This Kibana plugin contains components and services for field list UI (as in fie ## Components -* `` - loads and renders stats (Top values, Histogram) for a data view field. +* `` - loads and renders stats (Top values, Distribution) for a data view field. + +* `` - renders a button to open this field in Lens. + +* `` - a popover container component for a field. + +* `` - this header component included a field name and common actions. +* +* `` - renders Visualize action in the popover footer. + +These components can be combined and customized as the following: +``` +} + renderHeader={() => + + } + renderContent={() => + <> + + '} + ... + /> + + } + ... +/> +``` ## Public Services diff --git a/src/plugins/unified_field_list/kibana.json b/src/plugins/unified_field_list/kibana.json index 6785d55989cc..0615ee0473cc 100755 --- a/src/plugins/unified_field_list/kibana.json +++ b/src/plugins/unified_field_list/kibana.json @@ -9,7 +9,7 @@ "description": "Contains functionality for the field list which can be integrated into apps", "server": true, "ui": true, - "requiredPlugins": ["dataViews", "data", "fieldFormats", "charts"], + "requiredPlugins": ["dataViews", "data", "fieldFormats", "charts", "uiActions"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/src/plugins/unified_field_list/public/components/field_popover/field_popover.scss b/src/plugins/unified_field_list/public/components/field_popover/field_popover.scss new file mode 100644 index 000000000000..14ce3768f0be --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_popover/field_popover.scss @@ -0,0 +1,4 @@ +.unifiedFieldList__fieldPopover__fieldPopoverPanel { + min-width: $euiSizeXXL * 6.5; + max-width: $euiSizeXXL * 7.5; +} diff --git a/src/plugins/unified_field_list/public/components/field_popover/field_popover.test.tsx b/src/plugins/unified_field_list/public/components/field_popover/field_popover.test.tsx new file mode 100644 index 000000000000..bc5fe035ceff --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_popover/field_popover.test.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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, EuiPopoverTitle, EuiPopoverFooter } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { FieldPopover } from './field_popover'; +import { FieldPopoverHeader } from './field_popover_header'; + +describe('UnifiedFieldList ', () => { + it('should render correctly header only', async () => { + const wrapper = mountWithIntl( + } + renderHeader={() => {'header'}} + /> + ); + + expect(wrapper.find(EuiText).text()).toBe('header'); + expect(wrapper.find(EuiPopoverTitle)).toHaveLength(0); + expect(wrapper.find(EuiPopoverFooter)).toHaveLength(0); + }); + + it('should render correctly with header and content', async () => { + const wrapper = mountWithIntl( + } + renderHeader={() => {'header'}} + renderContent={() => {'content'}} + /> + ); + + expect(wrapper.find(EuiText).first().text()).toBe('header'); + expect(wrapper.find(EuiText).last().text()).toBe('content'); + expect(wrapper.find(EuiPopoverTitle)).toHaveLength(1); + }); + + it('should render nothing if popover is closed', async () => { + const wrapper = mountWithIntl( + } + renderHeader={() => {'header'}} + renderContent={() => {'content'}} + /> + ); + + expect(wrapper.text()).toBe(''); + expect(wrapper.find(EuiPopoverTitle)).toHaveLength(0); + }); + + it('should render correctly with popover header and content', async () => { + const mockClose = jest.fn(); + const mockEdit = jest.fn(); + const fieldName = 'extension'; + const wrapper = mountWithIntl( + } + renderHeader={() => ( + field.name === fieldName)!} + closePopover={mockClose} + onEditField={mockEdit} + /> + )} + renderContent={() => {'content'}} + /> + ); + + expect(wrapper.find(EuiPopoverTitle).text()).toBe(fieldName); + expect(wrapper.find(EuiText).last().text()).toBe('content'); + + wrapper + .find(`[data-test-subj="fieldPopoverHeader_editField-${fieldName}"]`) + .first() + .simulate('click'); + + expect(mockClose).toHaveBeenCalled(); + expect(mockEdit).toHaveBeenCalledWith(fieldName); + }); +}); diff --git a/src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx b/src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx new file mode 100644 index 000000000000..59c6b9621f8c --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx @@ -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 React from 'react'; +import { EuiPopover, EuiPopoverProps, EuiPopoverTitle } from '@elastic/eui'; +import './field_popover.scss'; + +export interface FieldPopoverProps extends EuiPopoverProps { + renderHeader?: () => React.ReactNode; + renderContent?: () => React.ReactNode; +} + +export const FieldPopover: React.FC = ({ + isOpen, + closePopover, + renderHeader, + renderContent, + ...otherPopoverProps +}) => { + const header = (isOpen && renderHeader?.()) || null; + const content = (isOpen && renderContent?.()) || null; + + return ( + + {isOpen && ( + <> + {content && header ? {header} : header} + {content} + + )} + + ); +}; diff --git a/src/plugins/unified_field_list/public/components/field_popover/field_popover_header.test.tsx b/src/plugins/unified_field_list/public/components/field_popover/field_popover_header.test.tsx new file mode 100644 index 000000000000..22d3269b2c0d --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_popover/field_popover_header.test.tsx @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiButtonIcon } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { FieldPopoverHeader } from './field_popover_header'; + +describe('UnifiedFieldList ', () => { + it('should render correctly without actions', async () => { + const mockClose = jest.fn(); + const fieldName = 'extension'; + const wrapper = mountWithIntl( + field.name === fieldName)!} + closePopover={mockClose} + /> + ); + + expect(wrapper.text()).toBe(fieldName); + expect(wrapper.find(EuiButtonIcon)).toHaveLength(0); + }); + + it('should render correctly with all actions', async () => { + const mockClose = jest.fn(); + const fieldName = 'extension'; + const field = dataView.fields.find((f) => f.name === fieldName)!; + jest.spyOn(field, 'isRuntimeField', 'get').mockImplementation(() => true); + const wrapper = mountWithIntl( + + ); + + expect(wrapper.text()).toBe(fieldName); + expect( + wrapper.find(`[data-test-subj="fieldPopoverHeader_addField-${fieldName}"]`).exists() + ).toBeTruthy(); + expect( + wrapper.find(`[data-test-subj="fieldPopoverHeader_addExistsFilter-${fieldName}"]`).exists() + ).toBeTruthy(); + expect( + wrapper.find(`[data-test-subj="fieldPopoverHeader_editField-${fieldName}"]`).exists() + ).toBeTruthy(); + expect( + wrapper.find(`[data-test-subj="fieldPopoverHeader_deleteField-${fieldName}"]`).exists() + ).toBeTruthy(); + expect(wrapper.find(EuiButtonIcon)).toHaveLength(4); + }); + + it('should correctly handle add-field action', async () => { + const mockClose = jest.fn(); + const mockAddField = jest.fn(); + const fieldName = 'extension'; + const field = dataView.fields.find((f) => f.name === fieldName)!; + const wrapper = mountWithIntl( + + ); + + wrapper + .find(`[data-test-subj="fieldPopoverHeader_addField-${fieldName}"]`) + .first() + .simulate('click'); + + expect(mockClose).toHaveBeenCalled(); + expect(mockAddField).toHaveBeenCalledWith(field); + }); + + it('should correctly handle add-exists-filter action', async () => { + const mockClose = jest.fn(); + const mockAddFilter = jest.fn(); + const fieldName = 'extension'; + const field = dataView.fields.find((f) => f.name === fieldName)!; + + // available + let wrapper = mountWithIntl( + + ); + wrapper + .find(`[data-test-subj="fieldPopoverHeader_addExistsFilter-${fieldName}"]`) + .first() + .simulate('click'); + expect(mockClose).toHaveBeenCalled(); + expect(mockAddFilter).toHaveBeenCalledWith('_exists_', fieldName, '+'); + + // hidden + jest.spyOn(field, 'filterable', 'get').mockImplementation(() => false); + wrapper = mountWithIntl( + + ); + expect( + wrapper.find(`[data-test-subj="fieldPopoverHeader_addExistsFilter-${fieldName}"]`).exists() + ).toBeFalsy(); + }); + + it('should correctly handle edit-field action', async () => { + const mockClose = jest.fn(); + const mockEditField = jest.fn(); + const fieldName = 'extension'; + const field = dataView.fields.find((f) => f.name === fieldName)!; + + // available + jest.spyOn(field, 'isRuntimeField', 'get').mockImplementation(() => true); + let wrapper = mountWithIntl( + + ); + wrapper + .find(`[data-test-subj="fieldPopoverHeader_editField-${fieldName}"]`) + .first() + .simulate('click'); + expect(mockClose).toHaveBeenCalled(); + expect(mockEditField).toHaveBeenCalledWith(fieldName); + + // hidden + jest.spyOn(field, 'isRuntimeField', 'get').mockImplementation(() => false); + jest.spyOn(field, 'type', 'get').mockImplementation(() => 'unknown'); + wrapper = mountWithIntl( + + ); + expect( + wrapper.find(`[data-test-subj="fieldPopoverHeader_editField-${fieldName}"]`).exists() + ).toBeFalsy(); + }); + + it('should correctly handle delete-field action', async () => { + const mockClose = jest.fn(); + const mockDeleteField = jest.fn(); + const fieldName = 'extension'; + const field = dataView.fields.find((f) => f.name === fieldName)!; + + // available + jest.spyOn(field, 'isRuntimeField', 'get').mockImplementation(() => true); + let wrapper = mountWithIntl( + + ); + wrapper + .find(`[data-test-subj="fieldPopoverHeader_deleteField-${fieldName}"]`) + .first() + .simulate('click'); + expect(mockClose).toHaveBeenCalled(); + expect(mockDeleteField).toHaveBeenCalledWith(fieldName); + + // hidden + jest.spyOn(field, 'isRuntimeField', 'get').mockImplementation(() => false); + wrapper = mountWithIntl( + + ); + expect( + wrapper.find(`[data-test-subj="fieldPopoverHeader_deleteField-${fieldName}"]`).exists() + ).toBeFalsy(); + }); +}); diff --git a/src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx b/src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx new file mode 100644 index 000000000000..131273fbb4d4 --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { + EuiButtonIcon, + EuiButtonIconProps, + EuiFlexGroup, + EuiFlexItem, + EuiPopoverProps, + EuiToolTip, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; +import type { AddFieldFilterHandler } from '../../types'; + +export interface FieldPopoverHeaderProps { + field: DataViewField; + closePopover: EuiPopoverProps['closePopover']; + buttonAddFieldToWorkspaceProps?: Partial; + buttonAddFilterProps?: Partial; + buttonEditFieldProps?: Partial; + buttonDeleteFieldProps?: Partial; + onAddFieldToWorkspace?: (field: DataViewField) => unknown; + onAddFilter?: AddFieldFilterHandler; + onEditField?: (fieldName: string) => unknown; + onDeleteField?: (fieldName: string) => unknown; +} + +export const FieldPopoverHeader: React.FC = ({ + field, + closePopover, + buttonAddFieldToWorkspaceProps, + buttonAddFilterProps, + buttonEditFieldProps, + buttonDeleteFieldProps, + onAddFieldToWorkspace, + onAddFilter, + onEditField, + onDeleteField, +}) => { + if (!field) { + return null; + } + + const addFieldToWorkspaceTooltip = i18n.translate( + 'unifiedFieldList.fieldPopover.addFieldToWorkspaceLabel', + { + defaultMessage: 'Add "{field}" field', + values: { + field: field.displayName, + }, + } + ); + + const addExistsFilterTooltip = i18n.translate( + 'unifiedFieldList.fieldPopover.addExistsFilterLabel', + { + defaultMessage: 'Filter for field present', + } + ); + + const editFieldTooltip = i18n.translate('unifiedFieldList.fieldPopover.editFieldLabel', { + defaultMessage: 'Edit data view field', + }); + + const deleteFieldTooltip = i18n.translate('unifiedFieldList.fieldPopover.deleteFieldLabel', { + defaultMessage: 'Delete data view field', + }); + + return ( + + + +
{field.displayName}
+
+
+ {onAddFieldToWorkspace && ( + + + { + closePopover(); + onAddFieldToWorkspace(field); + }} + /> + + + )} + {onAddFilter && field.filterable && !field.scripted && ( + + + { + closePopover(); + onAddFilter('_exists_', field.name, '+'); + }} + /> + + + )} + {onEditField && + (field.isRuntimeField || !['unknown', 'unknown_selected'].includes(field.type)) && ( + + + { + closePopover(); + onEditField(field.name); + }} + /> + + + )} + {onDeleteField && field.isRuntimeField && ( + + + { + closePopover(); + onDeleteField(field.name); + }} + /> + + + )} +
+ ); +}; diff --git a/src/plugins/unified_field_list/public/components/field_popover/field_popover_visualize.tsx b/src/plugins/unified_field_list/public/components/field_popover/field_popover_visualize.tsx new file mode 100644 index 000000000000..0f6b06c6239c --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_popover/field_popover_visualize.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiPopoverFooter } from '@elastic/eui'; +import { FieldVisualizeButton, type FieldVisualizeButtonProps } from '../field_visualize_button'; + +export type FieldPopoverVisualizeProps = Omit; + +const wrapInContainer = (element: React.ReactElement): React.ReactElement => { + return {element}; +}; + +export const FieldPopoverVisualize: React.FC = (props) => { + return ; +}; diff --git a/src/plugins/unified_field_list/public/components/field_popover/index.tsx b/src/plugins/unified_field_list/public/components/field_popover/index.tsx new file mode 100755 index 000000000000..ecbcf36d8331 --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_popover/index.tsx @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { type FieldPopoverProps, FieldPopover } from './field_popover'; +export { type FieldPopoverHeaderProps, FieldPopoverHeader } from './field_popover_header'; +export { type FieldPopoverVisualizeProps, FieldPopoverVisualize } from './field_popover_visualize'; diff --git a/src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button.test.tsx b/src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button.test.tsx new file mode 100644 index 000000000000..99f0543f536b --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button.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 and the Server Side Public License, v 1; you may not 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 { act } from 'react-dom/test-utils'; +import { ReactWrapper } from 'enzyme'; +import { EuiButton, EuiPopoverFooter } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; +import { FieldVisualizeButton } from './field_visualize_button'; +import { + ACTION_VISUALIZE_LENS_FIELD, + VISUALIZE_FIELD_TRIGGER, + VISUALIZE_GEO_FIELD_TRIGGER, + createAction, + VisualizeFieldContext, +} from '@kbn/ui-actions-plugin/public'; +import { TriggerContract } from '@kbn/ui-actions-plugin/public/triggers'; + +const ORIGINATING_APP = 'test'; +const mockExecuteAction = jest.fn(); +const uiActions = uiActionsPluginMock.createStartContract(); +const visualizeAction = createAction({ + type: ACTION_VISUALIZE_LENS_FIELD, + id: ACTION_VISUALIZE_LENS_FIELD, + getDisplayName: () => 'test', + isCompatible: async () => true, + execute: async (context: VisualizeFieldContext) => { + mockExecuteAction(context); + }, + getHref: async () => '/app/test', +}); + +jest.spyOn(uiActions, 'getTriggerCompatibleActions').mockResolvedValue([visualizeAction]); +jest.spyOn(uiActions, 'getTrigger').mockReturnValue({ + id: ACTION_VISUALIZE_LENS_FIELD, + exec: mockExecuteAction, +} as unknown as TriggerContract); + +describe('UnifiedFieldList ', () => { + it('should render correctly', async () => { + const fieldName = 'extension'; + const field = dataView.fields.find((f) => f.name === fieldName)!; + const fieldNameKeyword = 'extension.keyword'; + const fieldKeyword = dataView.fields.find((f) => f.name === fieldNameKeyword)!; + const contextualFields = ['bytes']; + jest.spyOn(field, 'visualizable', 'get').mockImplementationOnce(() => false); + jest.spyOn(fieldKeyword, 'visualizable', 'get').mockImplementationOnce(() => true); + let wrapper: ReactWrapper; + + await act(async () => { + wrapper = await mountWithIntl( + {element}} + /> + ); + }); + + await wrapper!.update(); + + expect(uiActions.getTriggerCompatibleActions).toHaveBeenCalledWith(VISUALIZE_FIELD_TRIGGER, { + contextualFields, + dataViewSpec: dataView.toSpec(false), + fieldName: fieldNameKeyword, + }); + + expect(wrapper!.text()).toBe('Visualize'); + wrapper!.find(EuiButton).simulate('click'); + + expect(mockExecuteAction).toHaveBeenCalledWith({ + contextualFields, + dataViewSpec: dataView.toSpec(false), + fieldName: fieldNameKeyword, + originatingApp: ORIGINATING_APP, + }); + + expect(wrapper!.find(EuiButton).prop('href')).toBe('/app/test'); + expect(wrapper!.find(EuiPopoverFooter).find(EuiButton).exists()).toBeTruthy(); // wrapped in a container + }); + + it('should render correctly for geo fields', async () => { + const fieldName = 'geo.coordinates'; + const field = dataView.fields.find((f) => f.name === fieldName)!; + jest.spyOn(field, 'visualizable', 'get').mockImplementationOnce(() => true); + let wrapper: ReactWrapper; + + await act(async () => { + wrapper = await mountWithIntl( + + ); + }); + + await wrapper!.update(); + + expect(uiActions.getTriggerCompatibleActions).toHaveBeenCalledWith( + VISUALIZE_GEO_FIELD_TRIGGER, + { + contextualFields: [], + dataViewSpec: dataView.toSpec(false), + fieldName, + } + ); + + expect(wrapper!.text()).toBe('Visualize'); + wrapper!.find(EuiButton).simulate('click'); + + expect(mockExecuteAction).toHaveBeenCalledWith({ + contextualFields: [], + dataViewSpec: dataView.toSpec(false), + fieldName, + originatingApp: ORIGINATING_APP, + }); + }); +}); diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize.tsx b/src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button.tsx similarity index 51% rename from src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize.tsx rename to src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button.tsx index f60838d375b6..aa9fe5266de0 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize.tsx +++ b/src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button.tsx @@ -7,29 +7,48 @@ */ import React, { useEffect, useState } from 'react'; +import { EuiButtonProps } from '@elastic/eui'; import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; -import { triggerVisualizeActions, VisualizeInformation } from './lib/visualize_trigger_utils'; -import { getVisualizeInformation } from './lib/visualize_trigger_utils'; -import { DiscoverFieldVisualizeInner } from './discover_field_visualize_inner'; +import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { FieldVisualizeButtonInner } from './field_visualize_button_inner'; +import { + triggerVisualizeActions, + getVisualizeInformation, + type VisualizeInformation, +} from './visualize_trigger_utils'; -interface Props { +export interface FieldVisualizeButtonProps { field: DataViewField; dataView: DataView; + originatingApp: string; // plugin id + uiActions: UiActionsStart; multiFields?: DataViewField[]; - contextualFields: string[]; + contextualFields?: string[]; // names of fields which were also selected (like columns in Discover grid) trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; + buttonProps?: Partial; + wrapInContainer?: (element: React.ReactElement) => React.ReactElement; } -export const DiscoverFieldVisualize: React.FC = React.memo( - ({ field, dataView, contextualFields, trackUiMetric, multiFields }) => { +export const FieldVisualizeButton: React.FC = React.memo( + ({ + field, + dataView, + contextualFields, + trackUiMetric, + multiFields, + originatingApp, + uiActions, + buttonProps, + wrapInContainer, + }) => { const [visualizeInfo, setVisualizeInfo] = useState(); useEffect(() => { - getVisualizeInformation(field, dataView, contextualFields, multiFields).then( + getVisualizeInformation(uiActions, field, dataView, contextualFields, multiFields).then( setVisualizeInfo ); - }, [contextualFields, field, dataView, multiFields]); + }, [contextualFields, field, dataView, multiFields, uiActions]); if (!visualizeInfo) { return null; @@ -43,17 +62,26 @@ export const DiscoverFieldVisualize: React.FC = React.memo( const triggerVisualization = (updatedDataView: DataView) => { trackUiMetric?.(METRIC_TYPE.CLICK, 'visualize_link_click'); - triggerVisualizeActions(visualizeInfo.field, contextualFields, updatedDataView); + triggerVisualizeActions( + uiActions, + visualizeInfo.field, + contextualFields, + originatingApp, + updatedDataView + ); }; triggerVisualization(dataView); }; - return ( - ); + + return wrapInContainer?.(element) || element; } ); diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize_inner.tsx b/src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button_inner.tsx similarity index 68% rename from src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize_inner.tsx rename to src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button_inner.tsx index 2bbdeed13fde..a8185839925b 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field_visualize_inner.tsx +++ b/src/plugins/unified_field_list/public/components/field_visualize_button/field_visualize_button_inner.tsx @@ -7,35 +7,40 @@ */ import React from 'react'; -import { EuiButton, EuiPopoverFooter } from '@elastic/eui'; +import { EuiButton, EuiButtonProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { DataViewField } from '@kbn/data-views-plugin/public'; -import { VisualizeInformation } from './lib/visualize_trigger_utils'; +import { VisualizeInformation } from './visualize_trigger_utils'; -interface DiscoverFieldVisualizeInnerProps { +interface FieldVisualizeButtonInnerProps { field: DataViewField; visualizeInfo: VisualizeInformation; handleVisualizeLinkClick: (event: React.MouseEvent) => void; + buttonProps?: Partial; } -export const DiscoverFieldVisualizeInner = (props: DiscoverFieldVisualizeInnerProps) => { - const { field, visualizeInfo, handleVisualizeLinkClick } = props; - +export const FieldVisualizeButtonInner: React.FC = ({ + field, + visualizeInfo, + handleVisualizeLinkClick, + buttonProps, +}) => { return ( - + <> {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - + ); }; diff --git a/src/plugins/unified_field_list/public/components/field_visualize_button/index.tsx b/src/plugins/unified_field_list/public/components/field_visualize_button/index.tsx new file mode 100755 index 000000000000..812ffbcb0d0e --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_visualize_button/index.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { type FieldVisualizeButtonProps, FieldVisualizeButton } from './field_visualize_button'; + +export { + triggerVisualizeActions, + triggerVisualizeActionsTextBasedLanguages, + getVisualizeInformation, + type VisualizeInformation, +} from './visualize_trigger_utils'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.test.ts b/src/plugins/unified_field_list/public/components/field_visualize_button/visualize_trigger_utils.test.ts similarity index 87% rename from src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.test.ts rename to src/plugins/unified_field_list/public/components/field_visualize_button/visualize_trigger_utils.test.ts index 9c2528bca748..da87ff5f0534 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.test.ts +++ b/src/plugins/unified_field_list/public/components/field_visualize_button/visualize_trigger_utils.test.ts @@ -7,7 +7,7 @@ */ import type { DataViewField, DataView } from '@kbn/data-views-plugin/public'; -import type { Action } from '@kbn/ui-actions-plugin/public'; +import type { Action, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { getVisualizeInformation } from './visualize_trigger_utils'; const field = { @@ -26,11 +26,9 @@ const mockGetActions = jest.fn>>, [string, { fieldN () => Promise.resolve([]) ); -jest.mock('../../../../../kibana_services', () => ({ - getUiActions: () => ({ - getTriggerCompatibleActions: mockGetActions, - }), -})); +const uiActions = { + getTriggerCompatibleActions: mockGetActions, +} as unknown as UiActionsStart; const action: Action = { id: 'action', @@ -51,7 +49,13 @@ describe('visualize_trigger_utils', () => { describe('getVisualizeInformation', () => { it('should return for a visualizeable field with an action', async () => { mockGetActions.mockResolvedValue([action]); - const information = await getVisualizeInformation(field, dataViewMock, [], undefined); + const information = await getVisualizeInformation( + uiActions, + field, + dataViewMock, + [], + undefined + ); expect(information).not.toBeUndefined(); expect(information?.field).toHaveProperty('name', 'fieldName'); expect(information?.href).toBeUndefined(); @@ -59,7 +63,13 @@ describe('visualize_trigger_utils', () => { it('should return field and href from the action', async () => { mockGetActions.mockResolvedValue([{ ...action, getHref: () => Promise.resolve('hreflink') }]); - const information = await getVisualizeInformation(field, dataViewMock, [], undefined); + const information = await getVisualizeInformation( + uiActions, + field, + dataViewMock, + [], + undefined + ); expect(information).not.toBeUndefined(); expect(information?.field).toHaveProperty('name', 'fieldName'); expect(information).toHaveProperty('href', 'hreflink'); @@ -68,6 +78,7 @@ describe('visualize_trigger_utils', () => { it('should return undefined if no field has a compatible action', async () => { mockGetActions.mockResolvedValue([]); const information = await getVisualizeInformation( + uiActions, { ...field, name: 'rootField' } as DataViewField, dataViewMock, [], @@ -82,6 +93,7 @@ describe('visualize_trigger_utils', () => { it('should return information for the root field, when multi fields and root are having actions', async () => { mockGetActions.mockResolvedValue([action]); const information = await getVisualizeInformation( + uiActions, { ...field, name: 'rootField' } as DataViewField, dataViewMock, [], @@ -102,6 +114,7 @@ describe('visualize_trigger_utils', () => { return []; }); const information = await getVisualizeInformation( + uiActions, { ...field, name: 'rootField' } as DataViewField, dataViewMock, [], diff --git a/src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.ts b/src/plugins/unified_field_list/public/components/field_visualize_button/visualize_trigger_utils.ts similarity index 84% rename from src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.ts rename to src/plugins/unified_field_list/public/components/field_visualize_button/visualize_trigger_utils.ts index 85537df2ef36..babb7f40ff92 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.ts +++ b/src/plugins/unified_field_list/public/components/field_visualize_button/visualize_trigger_utils.ts @@ -7,6 +7,7 @@ */ import { + type UiActionsStart, VISUALIZE_FIELD_TRIGGER, VISUALIZE_GEO_FIELD_TRIGGER, visualizeFieldTrigger, @@ -15,8 +16,6 @@ import { import type { AggregateQuery } from '@kbn/es-query'; import type { DataViewField, DataView } from '@kbn/data-views-plugin/public'; import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; -import { getUiActions } from '../../../../../kibana_services'; -import { PLUGIN_ID } from '../../../../../../common'; export function getTriggerConstant(type: string) { return type === KBN_FIELD_TYPES.GEO_POINT || type === KBN_FIELD_TYPES.GEO_SHAPE @@ -31,12 +30,13 @@ function getTrigger(type: string) { } async function getCompatibleActions( + uiActions: UiActionsStart, fieldName: string, dataView: DataView, - contextualFields: string[], + contextualFields: string[] = [], trigger: typeof VISUALIZE_FIELD_TRIGGER | typeof VISUALIZE_GEO_FIELD_TRIGGER ) { - const compatibleActions = await getUiActions().getTriggerCompatibleActions(trigger, { + const compatibleActions = await uiActions.getTriggerCompatibleActions(trigger, { dataViewSpec: dataView.toSpec(false), fieldName, contextualFields, @@ -45,8 +45,10 @@ async function getCompatibleActions( } export function triggerVisualizeActions( + uiActions: UiActionsStart, field: DataViewField, - contextualFields: string[], + contextualFields: string[] = [], + originatingApp: string, dataView?: DataView ) { if (!dataView) return; @@ -55,13 +57,15 @@ export function triggerVisualizeActions( dataViewSpec: dataView.toSpec(false), fieldName: field.name, contextualFields, - originatingApp: PLUGIN_ID, + originatingApp, }; - getUiActions().getTrigger(trigger).exec(triggerOptions); + uiActions.getTrigger(trigger).exec(triggerOptions); } export function triggerVisualizeActionsTextBasedLanguages( + uiActions: UiActionsStart, contextualFields: string[], + originatingApp: string, dataView?: DataView, query?: AggregateQuery ) { @@ -70,10 +74,10 @@ export function triggerVisualizeActionsTextBasedLanguages( dataViewSpec: dataView.toSpec(false), fieldName: '', contextualFields, - originatingApp: PLUGIN_ID, + originatingApp, query, }; - getUiActions().getTrigger(VISUALIZE_FIELD_TRIGGER).exec(triggerOptions); + uiActions.getTrigger(VISUALIZE_FIELD_TRIGGER).exec(triggerOptions); } export interface VisualizeInformation { @@ -86,9 +90,10 @@ export interface VisualizeInformation { * that has a compatible visualize uiAction. */ export async function getVisualizeInformation( + uiActions: UiActionsStart, field: DataViewField, dataView: DataView | undefined, - contextualFields: string[], + contextualFields: string[] = [], multiFields: DataViewField[] = [] ): Promise { if (field.name === '_id' || !dataView?.id) { @@ -102,6 +107,7 @@ export async function getVisualizeInformation( } // Retrieve compatible actions for the specific field const actions = await getCompatibleActions( + uiActions, f.name, dataView, contextualFields, @@ -111,7 +117,7 @@ export async function getVisualizeInformation( // if the field has compatible actions use this field for visualizing if (actions.length > 0) { const triggerOptions = { - dataViewSpec: dataView?.toSpec(), + dataViewSpec: dataView?.toSpec(false), fieldName: f.name, contextualFields, trigger: getTrigger(f.type), diff --git a/src/plugins/unified_field_list/public/index.ts b/src/plugins/unified_field_list/public/index.ts index bf1cd2175689..2ada1027ee97 100755 --- a/src/plugins/unified_field_list/public/index.ts +++ b/src/plugins/unified_field_list/public/index.ts @@ -16,6 +16,22 @@ export type { } from '../common/types'; export type { FieldStatsProps, FieldStatsServices } from './components/field_stats'; export { FieldStats } from './components/field_stats'; +export { + FieldPopover, + type FieldPopoverProps, + FieldPopoverHeader, + type FieldPopoverHeaderProps, + FieldPopoverVisualize, + type FieldPopoverVisualizeProps, +} from './components/field_popover'; +export { + FieldVisualizeButton, + type FieldVisualizeButtonProps, + getVisualizeInformation, + triggerVisualizeActions, + triggerVisualizeActionsTextBasedLanguages, + type VisualizeInformation, +} from './components/field_visualize_button'; export { loadFieldStats } from './services/field_stats'; export { loadFieldExisting } from './services/field_existing'; diff --git a/src/plugins/unified_field_list/public/types.ts b/src/plugins/unified_field_list/public/types.ts index dcd425b8a880..f7a712534d59 100755 --- a/src/plugins/unified_field_list/public/types.ts +++ b/src/plugins/unified_field_list/public/types.ts @@ -14,4 +14,8 @@ export interface UnifiedFieldListPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface UnifiedFieldListPluginStart {} -export type AddFieldFilterHandler = (field: DataViewField, value: unknown, type: '+' | '-') => void; +export type AddFieldFilterHandler = ( + field: DataViewField | '_exists_', + value: unknown, + type: '+' | '-' +) => void; diff --git a/src/plugins/unified_field_list/tsconfig.json b/src/plugins/unified_field_list/tsconfig.json index 221729fbd2b7..eabadac9ef6f 100644 --- a/src/plugins/unified_field_list/tsconfig.json +++ b/src/plugins/unified_field_list/tsconfig.json @@ -18,6 +18,7 @@ { "path": "../kibana_react/tsconfig.json" }, { "path": "../data_views/tsconfig.json" }, { "path": "../data/tsconfig.json" }, - { "path": "../charts/tsconfig.json" } + { "path": "../charts/tsconfig.json" }, + { "path": "../ui_actions/tsconfig.json" } ] } diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx index 8497b599650b..362ff4a20916 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx @@ -13,6 +13,7 @@ import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks'; import { ChangeDataView } from './change_dataview'; import { DataViewPickerPropsExtended, TextBasedLanguages } from '.'; @@ -44,6 +45,8 @@ describe('DataView component', () => { storageValue: boolean, uiSettingValue: boolean = false ) { + const dataViewEditorMock = dataViewEditorPluginMock.createStartContract(); + (dataViewEditorMock.userPermissions.editDataView as jest.Mock).mockReturnValue(true); let dataMock = dataPluginMock.createStartContract(); dataMock = { ...dataMock, @@ -56,6 +59,7 @@ describe('DataView component', () => { const services = { data: dataMock, storage: getStorage(storageValue), + dataViewEditor: dataViewEditorMock, uiSettings: { get: jest.fn(() => uiSettingValue), }, diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index 72a2d8fea290..1e71da3a0b20 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -72,6 +72,7 @@ export function ChangeDataView({ onTextLangQuerySubmit, textBasedLanguage, isDisabled, + onEditDataView, onCreateDefaultAdHocDataView, }: DataViewPickerPropsExtended) { const { euiTheme } = useEuiTheme(); @@ -88,7 +89,7 @@ export function ChangeDataView({ const [selectedDataViewId, setSelectedDataViewId] = useState(currentDataViewId); const kibana = useKibana(); - const { application, data, storage } = kibana.services; + const { application, data, storage, dataViews, dataViewEditor } = kibana.services; const styles = changeDataViewStyles({ fullWidth: trigger.fullWidth }); const [isTextLangTransitionModalDismissed, setIsTextLangTransitionModalDismissed] = useState(() => Boolean(storage.get(TEXT_LANG_TRANSITION_MODAL_KEY)) @@ -189,21 +190,35 @@ export function ChangeDataView({ defaultMessage: 'Add a field to this data view', })} , - { - setPopoverIsOpen(false); - application.navigateToApp('management', { - path: `/kibana/indexPatterns/patterns/${currentDataViewId}`, - }); - }} - > - {i18n.translate('unifiedSearch.query.queryBar.indexPattern.manageFieldButton', { - defaultMessage: 'Manage this data view', - })} - , + onEditDataView || dataViewEditor.userPermissions.editDataView() ? ( + { + if (onEditDataView) { + const dataView = await dataViews.get(currentDataViewId!); + dataViewEditor.openEditor({ + editData: dataView, + onSave: (updatedDataView) => { + onEditDataView(updatedDataView); + }, + }); + } else { + application.navigateToApp('management', { + path: `/kibana/indexPatterns/patterns/${currentDataViewId}`, + }); + } + setPopoverIsOpen(false); + }} + > + {i18n.translate('unifiedSearch.query.queryBar.indexPattern.manageFieldButton', { + defaultMessage: 'Manage this data view', + })} + + ) : ( + + ), ); } diff --git a/src/plugins/unified_search/public/dataview_picker/index.tsx b/src/plugins/unified_search/public/dataview_picker/index.tsx index 9fb794d58e64..8ed524b32ea1 100644 --- a/src/plugins/unified_search/public/dataview_picker/index.tsx +++ b/src/plugins/unified_search/public/dataview_picker/index.tsx @@ -41,6 +41,11 @@ export interface DataViewPickerProps { * Callback that is called when the user changes the currently selected dataview. */ onChangeDataView: (newId: string) => void; + /** + * Callback that is called when the user edits the current data view via flyout. + * The first parameter is the updated data view stub without fetched fields + */ + onEditDataView?: (updatedDataViewStub: DataView) => void; /** * The id of the selected dataview. */ @@ -98,6 +103,7 @@ export const DataViewPicker = ({ currentDataViewId, adHocDataViews, onChangeDataView, + onEditDataView, onAddField, onDataViewCreated, trigger, @@ -114,6 +120,7 @@ export const DataViewPicker = ({ isMissingCurrent={isMissingCurrent} currentDataViewId={currentDataViewId} onChangeDataView={onChangeDataView} + onEditDataView={onEditDataView} onAddField={onAddField} onDataViewCreated={onDataViewCreated} onCreateDefaultAdHocDataView={onCreateDefaultAdHocDataView} diff --git a/src/plugins/unified_search/public/filters_builder/__mock__/filters.ts b/src/plugins/unified_search/public/filters_builder/__mock__/filters.ts index 03d5b4333cff..fae7dd7e9350 100644 --- a/src/plugins/unified_search/public/filters_builder/__mock__/filters.ts +++ b/src/plugins/unified_search/public/filters_builder/__mock__/filters.ts @@ -33,7 +33,7 @@ export const getFiltersMock = () => }, { meta: { - type: 'OR', + type: 'combined', params: [ { meta: { @@ -302,7 +302,7 @@ export const getDataThatNeedsNormalized = () => }, { meta: { - type: 'OR', + type: 'combined', params: [ { meta: { @@ -420,7 +420,7 @@ export const getDataAfterNormalized = () => }, { meta: { - type: 'OR', + type: 'combined', params: [ { meta: { @@ -515,7 +515,7 @@ export const getDataThatNeedNotNormalized = () => [ { meta: { - type: 'OR', + type: 'combined', params: [ { meta: { diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_utils.test.ts b/src/plugins/unified_search/public/filters_builder/filters_builder_utils.test.ts index 517a0cea4cce..56a0eb97b80b 100644 --- a/src/plugins/unified_search/public/filters_builder/filters_builder_utils.test.ts +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { buildEmptyFilter, Filter } from '@kbn/es-query'; +import { buildEmptyFilter, Filter, FilterItem } from '@kbn/es-query'; import { ConditionTypes } from '../utils'; import { getFilterByPath, @@ -16,7 +16,6 @@ import { moveFilter, normalizeFilters, } from './filters_builder_utils'; -import type { FilterItem } from '../utils'; import { getConditionalOperationType } from '../utils'; import { diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder_utils.ts b/src/plugins/unified_search/public/filters_builder/filters_builder_utils.ts index dbbc81824a47..4d28d091341b 100644 --- a/src/plugins/unified_search/public/filters_builder/filters_builder_utils.ts +++ b/src/plugins/unified_search/public/filters_builder/filters_builder_utils.ts @@ -7,10 +7,10 @@ */ import { DataViewField } from '@kbn/data-views-plugin/common'; -import type { Filter } from '@kbn/es-query'; +import type { Filter, FilterItem } from '@kbn/es-query'; import { cloneDeep } from 'lodash'; -import { ConditionTypes, getConditionalOperationType, isOrFilter, buildOrFilter } from '../utils'; -import type { FilterItem } from '../utils'; +import { buildCombinedFilter, isCombinedFilter } from '@kbn/es-query'; +import { ConditionTypes, getConditionalOperationType } from '../utils'; import type { Operator } from '../filter_bar/filter_editor'; const PATH_SEPARATOR = '.'; @@ -66,8 +66,8 @@ export const normalizeFilters = (filters: FilterItem[]) => { const doRecursive = (f: FilterItem, parent: FilterItem) => { if (Array.isArray(f)) { return normalizeArray(f, parent); - } else if (isOrFilter(f)) { - return normalizeOr(f); + } else if (isCombinedFilter(f)) { + return normalizeCombined(f); } return f; }; @@ -92,17 +92,17 @@ export const normalizeFilters = (filters: FilterItem[]) => { return Array.isArray(parent) ? partiallyNormalized.flat() : partiallyNormalized; }; - const normalizeOr = (orFilter: Filter): FilterItem => { - const orFilters = getGroupedFilters(orFilter); - if (orFilters.length < 2) { - return orFilters[0]; + const normalizeCombined = (combinedFilter: Filter): FilterItem => { + const combinedFilters = getGroupedFilters(combinedFilter); + if (combinedFilters.length < 2) { + return combinedFilters[0]; } return { - ...orFilter, + ...combinedFilter, meta: { - ...orFilter.meta, - params: doRecursive(orFilters, orFilter), + ...combinedFilter.meta, + params: doRecursive(combinedFilters, combinedFilter), }, }; }; @@ -138,7 +138,7 @@ export const addFilter = ( if (parentConditionType !== conditionalType) { if (conditionalType === ConditionTypes.OR) { - targetArray.splice(selector, 1, buildOrFilter([targetArray[selector], filter])); + targetArray.splice(selector, 1, buildCombinedFilter([targetArray[selector], filter])); } if (conditionalType === ConditionTypes.AND) { targetArray.splice(selector, 1, [targetArray[selector], filter]); diff --git a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx index 54343ec245ef..02c22fe20586 100644 --- a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx +++ b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx @@ -493,7 +493,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
{!isCompactFocused && ( @@ -566,7 +566,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ data-test-subj="unifiedTextLangEditor-expand" css={{ borderRadius: 0, - backgroundColor: '#e9edf3', + backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3', border: '1px solid rgb(17 43 134 / 10%) !important', }} /> @@ -602,7 +602,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ css={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0, - backgroundColor: '#e9edf3', + backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3', border: '1px solid rgb(17 43 134 / 10%) !important', borderLeft: 'transparent !important', }} diff --git a/src/plugins/unified_search/public/search_bar/search_bar.test.tsx b/src/plugins/unified_search/public/search_bar/search_bar.test.tsx index b47b2d81b780..518f9d1f16e1 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.test.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.test.tsx @@ -10,6 +10,7 @@ import React from 'react'; import SearchBar from './search_bar'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks'; import { I18nProvider } from '@kbn/i18n-react'; import { coreMock } from '@kbn/core/public/mocks'; @@ -83,6 +84,9 @@ function wrapSearchBarInContext(testProps: any) { intl: null as any, }; + const dataViewEditorMock = dataViewEditorPluginMock.createStartContract(); + (dataViewEditorMock.userPermissions.editDataView as jest.Mock).mockReturnValue(true); + const services = { uiSettings: startMock.uiSettings, savedObjects: startMock.savedObjects, @@ -111,6 +115,7 @@ function wrapSearchBarInContext(testProps: any) { }), }, }, + dataViewEditor: dataViewEditorMock, dataViews: { getIdsWithTitle: jest.fn(() => []), }, diff --git a/src/plugins/unified_search/public/types.ts b/src/plugins/unified_search/public/types.ts index 85f32a9d4b7e..5142cd323c13 100755 --- a/src/plugins/unified_search/public/types.ts +++ b/src/plugins/unified_search/public/types.ts @@ -7,6 +7,7 @@ */ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { ScreenshotModePluginStart } from '@kbn/screenshot-mode-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -87,5 +88,6 @@ export interface IUnifiedSearchPluginServices extends Partial { docLinks: DocLinksStart; data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; + dataViewEditor: DataViewEditorStart; usageCollection?: UsageCollectionStart; } diff --git a/src/plugins/unified_search/public/utils/or_filter.ts b/src/plugins/unified_search/public/utils/combined_filter.ts similarity index 54% rename from src/plugins/unified_search/public/utils/or_filter.ts rename to src/plugins/unified_search/public/utils/combined_filter.ts index 419e4f04d74a..05810ed55f7c 100644 --- a/src/plugins/unified_search/public/utils/or_filter.ts +++ b/src/plugins/unified_search/public/utils/combined_filter.ts @@ -6,20 +6,13 @@ * Side Public License, v 1. */ -// Methods from this file will be removed after they are moved to the package -import { buildEmptyFilter, Filter } from '@kbn/es-query'; +import { isCombinedFilter, FilterItem } from '@kbn/es-query'; export enum ConditionTypes { OR = 'OR', AND = 'AND', } -/** @internal **/ -export type FilterItem = Filter | FilterItem[]; - -/** to: @kbn/es-query **/ -export const isOrFilter = (filter: Filter) => Boolean(filter?.meta?.type === 'OR'); - /** * Defines a conditional operation type (AND/OR) from the filter otherwise returns undefined. * @param {FilterItem} filter @@ -27,21 +20,7 @@ export const isOrFilter = (filter: Filter) => Boolean(filter?.meta?.type === 'OR export const getConditionalOperationType = (filter: FilterItem) => { if (Array.isArray(filter)) { return ConditionTypes.AND; - } else if (isOrFilter(filter)) { + } else if (isCombinedFilter(filter)) { return ConditionTypes.OR; } }; - -/** to: @kbn/es-query **/ -export const buildOrFilter = (filters: FilterItem) => { - const filter = buildEmptyFilter(false); - - return { - ...filter, - meta: { - ...filter.meta, - type: 'OR', - params: filters, - }, - }; -}; diff --git a/src/plugins/unified_search/public/utils/index.ts b/src/plugins/unified_search/public/utils/index.ts index 395304c48a91..0e3ac5f05c20 100644 --- a/src/plugins/unified_search/public/utils/index.ts +++ b/src/plugins/unified_search/public/utils/index.ts @@ -9,10 +9,4 @@ export { onRaf } from './on_raf'; export { shallowEqual } from './shallow_equal'; -export type { FilterItem } from './or_filter'; -export { - ConditionTypes, - isOrFilter, - getConditionalOperationType, - buildOrFilter, -} from './or_filter'; +export { ConditionTypes, getConditionalOperationType } from './combined_filter'; diff --git a/src/plugins/vis_types/metric/kibana.json b/src/plugins/vis_types/metric/kibana.json index ab69f7843033..4d8f776d2a0b 100644 --- a/src/plugins/vis_types/metric/kibana.json +++ b/src/plugins/vis_types/metric/kibana.json @@ -4,11 +4,20 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["data", "visualizations", "charts", "expressions"], - "requiredBundles": ["visDefaultEditor"], + "requiredPlugins": [ + "data", + "visualizations", + "charts", + "expressions", + "dataViews" + ], + "requiredBundles": [ + "visDefaultEditor", + "kibanaUtils" + ], "owner": { "name": "Vis Editors", "githubTeam": "kibana-vis-editors" }, "description": "Registers the Metric aggregation-based visualization." -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts new file mode 100644 index 000000000000..97fb145e74a2 --- /dev/null +++ b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ColorSchemas } from '@kbn/charts-plugin/common'; +import { getConfiguration, getPercentageModeConfig } from '.'; +import { VisParams } from '../../types'; + +const mockGetPalette = jest.fn(); + +jest.mock('./palette', () => ({ + getPalette: jest.fn(() => mockGetPalette()), +})); + +const params: VisParams = { + addTooltip: false, + addLegend: false, + dimensions: {} as VisParams['dimensions'], + metric: { + percentageMode: false, + percentageFormatPattern: '', + useRanges: true, + colorSchema: ColorSchemas.Greys, + metricColorMode: 'Labels', + colorsRange: [ + { type: 'range', from: 0, to: 100 }, + { type: 'range', from: 100, to: 200 }, + { type: 'range', from: 200, to: 300 }, + ], + labels: {}, + invertColors: false, + style: {} as VisParams['metric']['style'], + }, + type: 'metric', +}; + +describe('getPercentageModeConfig', () => { + test('should return falsy percentage mode if percentage mode is off', () => { + expect(getPercentageModeConfig(params)).toEqual({ isPercentageMode: false }); + }); + + test('should return percentage mode config', () => { + expect( + getPercentageModeConfig({ ...params, metric: { ...params.metric, percentageMode: true } }) + ).toEqual({ isPercentageMode: true, min: 0, max: 300 }); + }); +}); + +describe('getConfiguration', () => { + const palette = { name: 'custom', params: { name: 'custom' }, type: 'palette' }; + + beforeEach(() => { + jest.clearAllMocks(); + mockGetPalette.mockReturnValue(palette); + }); + + test('shourd return correct configuration', () => { + const layerId = 'layer-id'; + const metric = 'metric-id'; + const bucket = 'bucket-id'; + const collapseFn = 'sum'; + expect( + getConfiguration(layerId, params, { + metrics: [metric], + buckets: [bucket], + columnsWithoutReferenced: [], + bucketCollapseFn: { [metric]: collapseFn }, + }) + ).toEqual({ + breakdownByAccessor: bucket, + collapseFn, + layerId, + layerType: 'data', + metricAccessor: metric, + palette, + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts new file mode 100644 index 000000000000..39e001c1b87b --- /dev/null +++ b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Column, MetricVisConfiguration } from '@kbn/visualizations-plugin/common'; +import { PercentageModeConfig } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { VisParams } from '../../types'; +import { getPalette } from './palette'; + +export const getPercentageModeConfig = (params: VisParams): PercentageModeConfig => { + if (!params.metric.percentageMode) { + return { isPercentageMode: false }; + } + const { colorsRange } = params.metric; + return { + isPercentageMode: true, + min: colorsRange[0].from, + max: colorsRange[colorsRange.length - 1].to, + }; +}; + +export const getConfiguration = ( + layerId: string, + params: VisParams, + { + metrics, + buckets, + columnsWithoutReferenced, + bucketCollapseFn, + }: { + metrics: string[]; + buckets: string[]; + columnsWithoutReferenced: Column[]; + bucketCollapseFn?: Record; + } +): MetricVisConfiguration => { + const [metricAccessor] = metrics; + const [breakdownByAccessor] = buckets; + return { + layerId, + layerType: 'data', + palette: getPalette(params), + metricAccessor, + breakdownByAccessor, + collapseFn: Object.values(bucketCollapseFn ?? {})[0], + }; +}; diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/palette.test.ts b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/palette.test.ts new file mode 100644 index 000000000000..7458a78b7b55 --- /dev/null +++ b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/palette.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ColorSchemas } from '@kbn/charts-plugin/common'; +import { VisParams } from '../../types'; +import { getPalette } from './palette'; + +describe('getPalette', () => { + const params: VisParams = { + addTooltip: false, + addLegend: false, + dimensions: {} as VisParams['dimensions'], + metric: { + percentageMode: false, + percentageFormatPattern: '', + useRanges: true, + colorSchema: ColorSchemas.Greys, + metricColorMode: 'Labels', + colorsRange: [ + { type: 'range', from: 0, to: 100 }, + { type: 'range', from: 100, to: 200 }, + { type: 'range', from: 200, to: 300 }, + ], + labels: {}, + invertColors: false, + style: {} as VisParams['metric']['style'], + }, + type: 'metric', + }; + + test('should return undefined if metricColorMode is `None`', () => { + const metricColorMode = 'None'; + const paramsWithNoneMetricColorMode: VisParams = { + ...params, + metric: { ...params.metric, metricColorMode }, + }; + expect(getPalette(paramsWithNoneMetricColorMode)).toBeUndefined(); + }); + + test('should return undefined if empty color ranges were passed', () => { + const paramsWithNoneMetricColorMode: VisParams = { + ...params, + metric: { ...params.metric, colorsRange: [] }, + }; + expect(getPalette(paramsWithNoneMetricColorMode)).toBeUndefined(); + }); + + test('should return correct palette', () => { + expect(getPalette(params)).toEqual({ + name: 'custom', + params: { + colorStops: [ + { color: '#FFFFFF', stop: 0 }, + { color: '#979797', stop: 100 }, + { color: '#000000', stop: 200 }, + ], + continuity: 'none', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 300, + rangeMin: 0, + rangeType: 'number', + reverse: false, + stops: [ + { color: '#FFFFFF', stop: 100 }, + { color: '#979797', stop: 200 }, + { color: '#000000', stop: 300 }, + ], + }, + type: 'palette', + }); + }); +}); diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/palette.ts b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/palette.ts new file mode 100644 index 000000000000..360de60199ef --- /dev/null +++ b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/palette.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import color from 'color'; +import { CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; +import { VisParams } from '../../types'; +import { getStopsWithColorsFromRanges } from '../../utils'; +import { PaletteConfig } from '../../utils/palette'; + +type ColorStopsWithMinMax = Pick< + CustomPaletteParams, + 'colorStops' | 'stops' | 'steps' | 'rangeMax' | 'rangeMin' | 'continuity' +>; + +const buildPaletteParams = ({ color: colors, stop }: PaletteConfig): ColorStopsWithMinMax => { + const colorsWithoutStartColor = colors.slice(1, colors.length); + return { + rangeMin: stop[0], + rangeMax: stop[stop.length - 1], + continuity: 'none', + colorStops: colorsWithoutStartColor.map((c, index) => ({ + color: color(c!).hex(), + stop: stop[index], + })), + stops: colorsWithoutStartColor.map((c, index) => ({ + color: color(c!).hex(), + stop: stop[index + 1], + })), + }; +}; + +const buildCustomPalette = ( + colorStopsWithMinMax: ColorStopsWithMinMax +): PaletteOutput => { + return { + name: 'custom', + params: { + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: Infinity, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + ...colorStopsWithMinMax, + }, + type: 'palette', + }; +}; + +export const getPalette = (params: VisParams): PaletteOutput | undefined => { + const { colorSchema, colorsRange, invertColors, metricColorMode } = params.metric; + + if (metricColorMode === 'None' || !(colorsRange && colorsRange.length)) { + return; + } + + const stopsWithColors = getStopsWithColorsFromRanges(colorsRange, colorSchema, invertColors); + return buildCustomPalette(buildPaletteParams(stopsWithColors)); +}; diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts new file mode 100644 index 000000000000..015b19157e91 --- /dev/null +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ColorSchemas } from '@kbn/charts-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { convertToLens } from '.'; +import { VisParams } from '../types'; + +const mockGetColumnsFromVis = jest.fn(); +const mockGetPercentageColumnFormulaColumn = jest.fn(); +const mockGetConfiguration = jest.fn().mockReturnValue({}); +const mockGetPercentageModeConfig = jest.fn(); + +jest.mock('../services', () => ({ + getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })), +})); + +jest.mock('@kbn/visualizations-plugin/public', () => ({ + convertToLensModule: Promise.resolve({ + getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()), + getPercentageColumnFormulaColumn: jest.fn(() => mockGetPercentageColumnFormulaColumn()), + }), + getDataViewByIndexPatternId: jest.fn(() => ({ id: 'index-pattern' })), +})); + +jest.mock('./configurations', () => ({ + getConfiguration: jest.fn(() => mockGetConfiguration()), + getPercentageModeConfig: jest.fn(() => mockGetPercentageModeConfig()), +})); + +const params: VisParams = { + addTooltip: false, + addLegend: false, + dimensions: {} as VisParams['dimensions'], + metric: { + percentageMode: false, + percentageFormatPattern: '', + useRanges: false, + colorSchema: ColorSchemas.Greys, + metricColorMode: 'None', + colorsRange: [], + labels: {}, + invertColors: false, + style: { + bgFill: '', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 10, + }, + }, + type: 'metric', +}; + +const vis = { + isHierarchical: () => false, + type: {}, + params, + data: {}, +} as unknown as Vis; + +const timefilter = { + getAbsoluteTime: () => {}, +} as any; + +describe('convertToLens', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null if getColumnsFromVis returns null', async () => { + mockGetColumnsFromVis.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if metrics count is more than 1', async () => { + mockGetColumnsFromVis.mockReturnValue({ + metrics: ['1', '2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + }); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + test('should return null if buckets count is more than 1', async () => { + mockGetColumnsFromVis.mockReturnValue({ + metrics: [], + buckets: ['1', '2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + }); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if metric column data type is different from number', async () => { + mockGetColumnsFromVis.mockReturnValue({ + metrics: ['1'], + buckets: ['2'], + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], + }); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + test('should return correct state for valid vis', async () => { + const config = { + layerType: 'data', + metricAccessor: '1', + }; + + mockGetColumnsFromVis.mockReturnValue({ + metrics: ['1'], + buckets: ['2'], + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }); + mockGetConfiguration.mockReturnValue(config); + + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetConfiguration).toBeCalledTimes(1); + + expect(result?.type).toEqual('lnsMetric'); + expect(result?.layers.length).toEqual(1); + expect(result?.layers[0]).toEqual( + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], + }) + ); + expect(result?.configuration).toEqual(config); + }); +}); diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts new file mode 100644 index 000000000000..7675cbcc1d71 --- /dev/null +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; +import { + convertToLensModule, + getDataViewByIndexPatternId, +} from '@kbn/visualizations-plugin/public'; +import uuid from 'uuid'; +import { getDataViewsStart } from '../services'; +import { ConvertMetricVisToLensVisualization } from './types'; + +export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { + if ((column as ColumnWithMeta).meta) { + return true; + } + return false; +}; + +export const excludeMetaFromColumn = (column: Column) => { + if (isColumnWithMeta(column)) { + const { meta, ...rest } = column; + return rest; + } + return column; +}; + +export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, timefilter) => { + if (!timefilter) { + return null; + } + + const dataViews = getDataViewsStart(); + const dataView = await getDataViewByIndexPatternId(vis.data.indexPattern?.id, dataViews); + + if (!dataView) { + return null; + } + + const [{ getColumnsFromVis }, { getConfiguration, getPercentageModeConfig }] = await Promise.all([ + convertToLensModule, + import('./configurations'), + ]); + + const result = getColumnsFromVis( + vis, + timefilter, + dataView, + { + splits: ['group'], + }, + { dropEmptyRowsInDateHistogram: true, ...getPercentageModeConfig(vis.params) } + ); + + if (result === null) { + return null; + } + + // for now, multiple metrics are not supported + if (result.metrics.length > 1 || result.buckets.length > 1) { + return null; + } + + if (result.metrics[0]) { + const metric = result.columns.find(({ columnId }) => columnId === result.metrics[0]); + if (metric?.dataType !== 'number') { + return null; + } + } + + const layerId = uuid(); + const indexPatternId = dataView.id!; + + return { + type: 'lnsMetric', + layers: [ + { + indexPatternId, + layerId, + columns: result.columns.map(excludeMetaFromColumn), + columnOrder: [], + }, + ], + configuration: getConfiguration(layerId, vis.params, result), + indexPatternIds: [indexPatternId], + }; +}; diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/types.ts b/src/plugins/vis_types/metric/public/convert_to_lens/types.ts new file mode 100644 index 000000000000..3676c9dee8d9 --- /dev/null +++ b/src/plugins/vis_types/metric/public/convert_to_lens/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { TimefilterContract } from '@kbn/data-plugin/public'; +import { NavigateToLensContext, MetricVisConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { VisParams } from '../types'; + +export type ConvertMetricVisToLensVisualization = ( + vis: Vis, + timefilter?: TimefilterContract +) => Promise | null>; diff --git a/src/plugins/vis_types/metric/public/metric_vis_type.ts b/src/plugins/vis_types/metric/public/metric_vis_type.ts index 30e13e8605b6..b39cde5e07fc 100644 --- a/src/plugins/vis_types/metric/public/metric_vis_type.ts +++ b/src/plugins/vis_types/metric/public/metric_vis_type.ts @@ -13,6 +13,7 @@ import { AggGroupNames } from '@kbn/data-plugin/public'; import { MetricVisOptions } from './components'; import { toExpressionAst } from './to_ast'; import { VisParams } from './types'; +import { convertToLens } from './convert_to_lens'; export const createMetricVisTypeDefinition = (): VisTypeDefinition => ({ name: 'metric', @@ -103,4 +104,10 @@ export const createMetricVisTypeDefinition = (): VisTypeDefinition => ], }, requiresSearch: true, + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, }); diff --git a/src/plugins/vis_types/metric/public/plugin.ts b/src/plugins/vis_types/metric/public/plugin.ts index 9e79d1d23efd..bf8b8af0bddd 100644 --- a/src/plugins/vis_types/metric/public/plugin.ts +++ b/src/plugins/vis_types/metric/public/plugin.ts @@ -8,8 +8,10 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { createMetricVisTypeDefinition } from './metric_vis_type'; import { ConfigSchema } from '../config'; +import { setDataViewsStart } from './services'; /** @internal */ export interface MetricVisPluginSetupDependencies { @@ -17,7 +19,14 @@ export interface MetricVisPluginSetupDependencies { } /** @internal */ -export class MetricVisPlugin implements Plugin { +export interface MetricVisPluginStartDependencies { + dataViews: DataViewsPublicPluginStart; +} + +/** @internal */ +export class MetricVisPlugin + implements Plugin +{ initializerContext: PluginInitializerContext; constructor(initializerContext: PluginInitializerContext) { @@ -28,5 +37,7 @@ export class MetricVisPlugin implements Plugin { visualizations.createBaseVisualization(createMetricVisTypeDefinition()); } - public start(core: CoreStart) {} + public start(core: CoreStart, { dataViews }: MetricVisPluginStartDependencies) { + setDataViewsStart(dataViews); + } } diff --git a/src/plugins/vis_types/metric/public/services.ts b/src/plugins/vis_types/metric/public/services.ts new file mode 100644 index 000000000000..736ad70d4941 --- /dev/null +++ b/src/plugins/vis_types/metric/public/services.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 { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +export const [getDataViewsStart, setDataViewsStart] = + createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.ts index e236c36e82a1..1b37e36f1d98 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.ts @@ -54,7 +54,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi buckets: ['bucket'], splits: ['split_row', 'split_column'], }, - { dropEmptyRowsInDateHistogram: true } + { dropEmptyRowsInDateHistogram: true, isPercentageMode: false } ); if (result === null) { diff --git a/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts b/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts index 487ad390a62d..a0885d792ad3 100644 --- a/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts +++ b/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts @@ -102,16 +102,9 @@ export function getTimelionRequestHandler({ const esQueryConfigs = getEsQueryConfig(uiSettings); - // parse the time range client side to make sure it behaves like other charts - const timeRangeBounds = timefilter.calculateBounds(timeRange); - const untrackSearch = - dataSearch.session.isCurrentSession(searchSessionId) && - dataSearch.session.trackSearch({ - abort: () => abortController.abort(), - }); - - try { - const searchSessionOptions = dataSearch.session.getSearchOptions(searchSessionId); + const doSearch = async ( + searchOptions: ReturnType + ): Promise => { return await http.post('/api/timelion/run', { body: JSON.stringify({ sheet: [expression], @@ -126,14 +119,40 @@ export function getTimelionRequestHandler({ interval: visParams.interval, timezone, }, - ...(searchSessionOptions && { - searchSession: searchSessionOptions, - }), + ...(searchOptions + ? { + searchSession: searchOptions, + } + : {}), }), context: executionContext, signal: abortController.signal, }); + }; + + // parse the time range client side to make sure it behaves like other charts + const timeRangeBounds = timefilter.calculateBounds(timeRange); + const searchTracker = dataSearch.session.isCurrentSession(searchSessionId) + ? dataSearch.session.trackSearch({ + abort: () => abortController.abort(), + poll: async () => { + // don't use, keep this empty, onSavingSession is used instead + }, + onSavingSession: async (searchSessionOptions) => { + await doSearch(searchSessionOptions); + }, + }) + : undefined; + + try { + const searchSessionOptions = dataSearch.session.getSearchOptions(searchSessionId); + const visData = await doSearch(searchSessionOptions); + + searchTracker?.complete(); + return visData; } catch (e) { + searchTracker?.error(); + if (e && e.body) { const err = new Error( `${i18n.translate('timelion.requestHandlerErrorTitle', { @@ -146,10 +165,6 @@ export function getTimelionRequestHandler({ throw e; } } finally { - if (untrackSearch && dataSearch.session.isCurrentSession(searchSessionId)) { - // call `untrack` if this search still belongs to current session - untrackSearch(); - } expressionAbortSignal.removeEventListener('abort', expressionAbortHandler); } }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts new file mode 100644 index 000000000000..cbc899981717 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { TSVB_METRIC_TYPES } from '../../../common/enums'; +import { Metric } from '../../../common/types'; +import { convertToLens } from '.'; +import { createPanel, createSeries } from '../lib/__mocks__'; +import { AvgColumn } from '../lib/convert'; + +const mockGetMetricsColumns = jest.fn(); +const mockGetBucketsColumns = jest.fn(); +const mockGetConfigurationForGauge = jest.fn(); +const mockIsValidMetrics = jest.fn(); +const mockGetDatasourceValue = jest + .fn() + .mockImplementation(() => Promise.resolve(stubLogstashDataView)); +const mockGetDataSourceInfo = jest.fn(); +const mockGetSeriesAgg = jest.fn(); + +jest.mock('../../services', () => ({ + getDataViewsStart: jest.fn(() => mockGetDatasourceValue), +})); + +jest.mock('../lib/series', () => ({ + getMetricsColumns: jest.fn(() => mockGetMetricsColumns()), + getBucketsColumns: jest.fn(() => mockGetBucketsColumns()), + getSeriesAgg: jest.fn(() => mockGetSeriesAgg()), +})); + +jest.mock('../lib/configurations/metric', () => ({ + getConfigurationForGauge: jest.fn(() => mockGetConfigurationForGauge()), +})); + +jest.mock('../lib/metrics', () => { + const actual = jest.requireActual('../lib/metrics'); + return { + isValidMetrics: jest.fn(() => mockIsValidMetrics()), + getReducedTimeRange: jest.fn().mockReturnValue('10'), + SUPPORTED_METRICS: actual.SUPPORTED_METRICS, + getFormulaFromMetric: actual.getFormulaFromMetric, + }; +}); + +jest.mock('../lib/datasource', () => ({ + getDataSourceInfo: jest.fn(() => mockGetDataSourceInfo()), +})); + +describe('convertToLens', () => { + const metric = { id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }; + const model = createPanel({ + series: [createSeries({ metrics: [metric] })], + }); + + const metricColumn: AvgColumn = { + columnId: 'col-id', + dataType: 'number', + isSplit: false, + isBucketed: false, + meta: { metricId: metric.id }, + operationType: 'average', + sourceField: metric.field, + params: {}, + reducedTimeRange: '10m', + }; + + beforeEach(() => { + mockIsValidMetrics.mockReturnValue(true); + mockGetDataSourceInfo.mockReturnValue({ + indexPatternId: 'test-index-pattern', + timeField: 'timeField', + indexPattern: { id: 'test-index-pattern' }, + }); + mockGetMetricsColumns.mockReturnValue([{}]); + mockGetBucketsColumns.mockReturnValue([{}]); + mockGetConfigurationForGauge.mockReturnValue({}); + mockGetSeriesAgg.mockReturnValue({ metrics: [] }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null for invalid metrics', async () => { + mockIsValidMetrics.mockReturnValue(null); + const result = await convertToLens(model); + expect(result).toBeNull(); + expect(mockIsValidMetrics).toBeCalledTimes(1); + }); + + test('should return null for invalid or unsupported metrics', async () => { + mockGetMetricsColumns.mockReturnValue(null); + const result = await convertToLens(model); + expect(result).toBeNull(); + expect(mockGetMetricsColumns).toBeCalledTimes(1); + }); + + test('should return null for invalid or unsupported buckets', async () => { + mockGetBucketsColumns.mockReturnValue(null); + const result = await convertToLens(model); + expect(result).toBeNull(); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + }); + + test('should return null if metric is staticValue', async () => { + const result = await convertToLens({ + ...model, + series: [ + { + ...model.series[0], + metrics: [...model.series[0].metrics, { type: TSVB_METRIC_TYPES.STATIC } as Metric], + }, + ], + }); + expect(result).toBeNull(); + expect(mockGetDataSourceInfo).toBeCalledTimes(0); + }); + + test('should return null if only series agg is specified', async () => { + const result = await convertToLens({ + ...model, + series: [ + { + ...model.series[0], + metrics: [ + { type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'min', id: 'some-id' } as Metric, + ], + }, + ], + }); + expect(result).toBeNull(); + }); + + test('should return null configuration is not valid', async () => { + mockGetMetricsColumns.mockReturnValue([metricColumn]); + mockGetSeriesAgg.mockReturnValue({ metrics: [metric] }); + mockGetConfigurationForGauge.mockReturnValue(null); + + const result = await convertToLens(model); + expect(result).toBeNull(); + }); + + test('should return state', async () => { + mockGetMetricsColumns.mockReturnValue([metricColumn]); + mockGetSeriesAgg.mockReturnValue({ metrics: [metric] }); + mockGetConfigurationForGauge.mockReturnValue({}); + + const result = await convertToLens( + createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: false, + }), + ], + }) + ); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsMetric'); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts new file mode 100644 index 000000000000..b97f8d59e953 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import uuid from 'uuid'; +import { parseTimeShift } from '@kbn/data-plugin/common'; +import { + FormulaColumn, + getIndexPatternIds, + StaticValueColumn, +} from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { PANEL_TYPES, TSVB_METRIC_TYPES } from '../../../common/enums'; +import { Metric } from '../../../common/types'; +import { getDataViewsStart } from '../../services'; +import { getDataSourceInfo } from '../lib/datasource'; +import { getMetricsColumns, getBucketsColumns } from '../lib/series'; +import { getConfigurationForGauge as getConfiguration } from '../lib/configurations/metric'; +import { + getFormulaFromMetric, + getReducedTimeRange, + isValidMetrics, + SUPPORTED_METRICS, +} from '../lib/metrics'; +import { ConvertTsvbToLensVisualization } from '../types'; +import { + Column, + createFormulaColumnWithoutMeta, + createStaticValueColumn, + Layer as ExtendedLayer, +} from '../lib/convert'; +import { excludeMetaFromLayers, findMetricColumn, getMetricWithCollapseFn } from '../utils'; + +const getMaxFormula = (metric: Metric, column?: Column) => { + const baseFormula = `overall_max`; + if (column && column.operationType === 'formula') { + return `${baseFormula}(${column.params.formula})`; + } + + return `${baseFormula}(${getFormulaFromMetric(SUPPORTED_METRICS[metric.type]!)}(${ + metric.field ?? '' + }))`; +}; + +export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeRange) => { + const dataViews = getDataViewsStart(); + + const series = model.series[0]; + // not valid time shift + if (series.offset_time && parseTimeShift(series.offset_time) === 'invalid') { + return null; + } + + if (!isValidMetrics(series.metrics, PANEL_TYPES.GAUGE, series.time_range_mode)) { + return null; + } + + if (series.metrics[series.metrics.length - 1].type === TSVB_METRIC_TYPES.STATIC) { + return null; + } + + const reducedTimeRange = getReducedTimeRange(model, series, timeRange); + const datasourceInfo = await getDataSourceInfo( + model.index_pattern, + model.time_field, + Boolean(series.override_index_pattern), + series.series_index_pattern, + series.series_time_field, + dataViews + ); + + if (!datasourceInfo) { + return null; + } + + const { indexPatternId, indexPattern } = datasourceInfo; + + // handle multiple metrics + const metricsColumns = getMetricsColumns(series, indexPattern!, model.series.length, { + reducedTimeRange, + }); + if (metricsColumns === null) { + return null; + } + + const bucketsColumns = getBucketsColumns(model, series, metricsColumns, indexPattern!, false); + + if (bucketsColumns === null) { + return null; + } + + const [bucket] = bucketsColumns; + + const extendedLayer: ExtendedLayer = { + indexPatternId, + layerId: uuid(), + columns: [...metricsColumns, ...(bucket ? [bucket] : [])], + columnOrder: [], + }; + + const primarySeries = model.series[0]; + const primaryMetricWithCollapseFn = getMetricWithCollapseFn(primarySeries); + + if (!primaryMetricWithCollapseFn || !primaryMetricWithCollapseFn.metric) { + return null; + } + + const primaryColumn = findMetricColumn(primaryMetricWithCollapseFn.metric, extendedLayer.columns); + if (!primaryColumn) { + return null; + } + + let gaugeMaxColumn: StaticValueColumn | FormulaColumn | null = createFormulaColumnWithoutMeta( + getMaxFormula(primaryMetricWithCollapseFn.metric, primaryColumn) + ); + if (model.gauge_max !== undefined && model.gauge_max !== '') { + gaugeMaxColumn = createStaticValueColumn(model.gauge_max); + } + + const layer = { + ...extendedLayer, + columns: [...extendedLayer.columns, gaugeMaxColumn], + }; + const configuration = getConfiguration(model, layer, bucket, gaugeMaxColumn ?? undefined); + if (!configuration) { + return null; + } + + const layers = Object.values(excludeMetaFromLayers({ 0: layer })); + return { + type: 'lnsMetric', + layers, + configuration, + indexPatternIds: getIndexPatternIds(layers), + }; +}; 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 a64118a1cb50..a3d08e89e91a 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 @@ -25,6 +25,10 @@ const getConvertFnByType = (type: PANEL_TYPES) => { const { convertToLens } = await import('./metric'); return convertToLens; }, + [PANEL_TYPES.GAUGE]: async () => { + const { convertToLens } = await import('./gauge'); + return convertToLens; + }, }; return convertionFns[type]?.(); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts new file mode 100644 index 000000000000..9cb0238f9d26 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.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 { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { TSVB_METRIC_TYPES } from '../../../../../common/enums'; +import { Column, FormulaColumn, Layer } from '../../convert'; +import { createPanel, createSeries } from '../../__mocks__'; +import { getConfigurationForMetric, getConfigurationForGauge } from '.'; + +const mockGetPalette = jest.fn(); + +jest.mock('./palette', () => ({ + getPalette: jest.fn(() => mockGetPalette()), +})); + +describe('getConfigurationForMetric', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockGetPalette.mockReturnValue(undefined); + }); + + const metricId = 'some-id'; + const metric = { id: metricId, type: METRIC_TYPES.COUNT }; + test('should return null if no series was provided', () => { + const layerId = 'layer-id-1'; + const model = createPanel({ series: [] }); + const layer: Layer = { + columns: [], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForMetric(model, layer); + + expect(config).toBeNull(); + expect(mockGetPalette).toBeCalledTimes(0); + }); + + test('should return null if only series agg', () => { + const layerId = 'layer-id-1'; + const metric1 = { id: 'metric-id-2', type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'min' }; + const model = createPanel({ + series: [createSeries({ metrics: [metric1] })], + }); + const layer: Layer = { + columns: [], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForMetric(model, layer); + + expect(config).toBeNull(); + expect(mockGetPalette).toBeCalledTimes(0); + }); + + test('should return null if multiple series aggs', () => { + const layerId = 'layer-id-1'; + const metric1 = { id: 'metric-id-1', type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'sum' }; + const metric2 = { id: 'metric-id-2', type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'min' }; + const model = createPanel({ + series: [ + createSeries({ metrics: [metric, metric1] }), + createSeries({ metrics: [metric, metric2] }), + ], + }); + const layer: Layer = { + columns: [], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForMetric(model, layer); + + expect(config).toBeNull(); + expect(mockGetPalette).toBeCalledTimes(0); + }); + + test('should return config if only one series agg is specified', () => { + const layerId = 'layer-id-1'; + const metricId1 = 'metric-id-1'; + + const metricId2 = 'metric-id-2'; + const metric1 = { id: metricId1, type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'sum' }; + const metric2 = { ...metric, id: metricId2 }; + const columnId1 = 'col-id-1'; + const columnId2 = 'col-id-2'; + + const model = createPanel({ + series: [createSeries({ metrics: [metric, metric1] }), createSeries({ metrics: [metric2] })], + }); + const layer: Layer = { + columns: [ + { + columnId: columnId1, + meta: { metricId }, + }, + { + columnId: columnId2, + meta: { metricId: metricId2 }, + }, + ] as Column[], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForMetric(model, layer); + + expect(config).toEqual({ + breakdownByAccessor: undefined, + collapseFn: 'sum', + layerId, + layerType: 'data', + metricAccessor: columnId1, + palette: undefined, + secondaryMetricAccessor: columnId2, + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); + + test('should return config for single metric', () => { + const layerId = 'layer-id-1'; + const columnId = 'col-id-1'; + const bucketColumnId = 'col-id-2'; + const model = createPanel({ + series: [createSeries({ metrics: [metric] })], + }); + const bucket = { columnId: bucketColumnId } as Column; + const layer: Layer = { + columns: [ + { + columnId, + operationType: 'count', + dataType: 'number', + params: {}, + sourceField: 'document', + isBucketed: false, + isSplit: false, + meta: { metricId }, + }, + ], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForMetric(model, layer, bucket); + + expect(config).toEqual({ + layerId, + layerType: 'data', + metricAccessor: columnId, + breakdownByAccessor: bucketColumnId, + collapseFn: undefined, + palette: undefined, + secondaryMetricAccessor: undefined, + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); + + test('should return null if palette is invalid', () => { + mockGetPalette.mockReturnValue(null); + const layerId = 'layer-id-1'; + const columnId = 'col-id-1'; + const model = createPanel({ + series: [createSeries({ metrics: [metric] })], + }); + const layer: Layer = { + columns: [ + { + columnId, + operationType: 'count', + dataType: 'number', + params: {}, + sourceField: 'document', + isBucketed: false, + isSplit: false, + meta: { metricId }, + }, + ], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForMetric(model, layer); + expect(config).toBeNull(); + expect(mockGetPalette).toBeCalledTimes(1); + }); +}); + +describe('getConfigurationForGauge', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockGetPalette.mockReturnValue(undefined); + }); + + const metricId = 'some-id'; + const maxColumnId = 'col-id-1'; + const metric = { id: metricId, type: METRIC_TYPES.COUNT }; + const gaugeMaxColumn: FormulaColumn = { + references: [], + columnId: maxColumnId, + operationType: 'formula', + isBucketed: false, + isSplit: false, + dataType: 'number', + params: { formula: '100' }, + meta: { metricId }, + }; + + test('should return null if no series was provided', () => { + const layerId = 'layer-id-1'; + const model = createPanel({ series: [] }); + const layer: Layer = { + columns: [], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn); + + expect(config).toBeNull(); + expect(mockGetPalette).toBeCalledTimes(0); + }); + + test('should return null if only series agg', () => { + const layerId = 'layer-id-1'; + const metric1 = { id: 'metric-id-2', type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'min' }; + const model = createPanel({ + series: [createSeries({ metrics: [metric1] })], + }); + const layer: Layer = { + columns: [], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn); + + expect(config).toBeNull(); + expect(mockGetPalette).toBeCalledTimes(0); + }); + + test('should return null if palette is invalid', () => { + mockGetPalette.mockReturnValueOnce(null); + const layerId = 'layer-id-1'; + const columnId = 'col-id-1'; + const model = createPanel({ + series: [createSeries({ metrics: [metric] })], + }); + const layer: Layer = { + columns: [ + { + columnId, + operationType: 'count', + dataType: 'number', + params: {}, + sourceField: 'document', + isBucketed: false, + isSplit: false, + meta: { metricId }, + }, + ], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn); + expect(config).toBeNull(); + expect(mockGetPalette).toBeCalledTimes(1); + }); + + test('should return config with color if palette is not valid', () => { + const layerId = 'layer-id-1'; + const metric1 = { id: 'metric-id-1', type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'sum' }; + const color = '#fff'; + const model = createPanel({ series: [createSeries({ metrics: [metric, metric1], color })] }); + const layer: Layer = { + columns: [], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn); + + expect(config).toEqual({ + breakdownByAccessor: undefined, + collapseFn: 'sum', + layerId: 'layer-id-1', + layerType: 'data', + metricAccessor: undefined, + palette: undefined, + maxAccessor: maxColumnId, + color: '#FFFFFF', + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); + + test('should return config with palette', () => { + const palette = { type: 'custom', name: 'default', params: {} }; + mockGetPalette.mockReturnValue(palette); + const layerId = 'layer-id-1'; + const columnId1 = 'col-id-1'; + + const metric1 = { id: 'metric-id-1', type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'sum' }; + const color = '#fff'; + const model = createPanel({ series: [createSeries({ metrics: [metric, metric1], color })] }); + const bucketColumnId = 'bucket-column-id-1'; + const bucket = { columnId: bucketColumnId } as Column; + const layer: Layer = { + columns: [ + { + columnId: columnId1, + operationType: 'count', + dataType: 'number', + params: {}, + sourceField: 'document', + isBucketed: false, + isSplit: false, + meta: { metricId }, + }, + ], + columnOrder: [], + indexPatternId: 'some-index-pattern', + layerId, + }; + const config = getConfigurationForGauge(model, layer, bucket, gaugeMaxColumn); + + expect(config).toEqual({ + breakdownByAccessor: bucket.columnId, + collapseFn: 'sum', + layerId, + layerType: 'data', + metricAccessor: columnId1, + palette, + maxAccessor: maxColumnId, + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts index d1f24485d764..7b49d604b234 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts @@ -6,28 +6,12 @@ * Side Public License, v 1. */ +import color from 'color'; import { MetricVisConfiguration } from '@kbn/visualizations-plugin/common'; -import { Metric, Panel, Series } from '../../../../../common/types'; +import { Panel } from '../../../../../common/types'; import { Column, Layer } from '../../convert'; -import { getSeriesAgg } from '../../series'; import { getPalette } from './palette'; - -const getMetricWithCollapseFn = (series: Series | undefined) => { - if (!series) { - return; - } - const { metrics, seriesAgg } = getSeriesAgg(series.metrics); - const visibleMetric = metrics[metrics.length - 1]; - return { metric: visibleMetric, collapseFn: seriesAgg }; -}; - -const findMetricColumn = (metric: Metric | undefined, columns: Column[]) => { - if (!metric) { - return; - } - - return columns.find((column) => 'meta' in column && column.meta.metricId === metric.id); -}; +import { findMetricColumn, getMetricWithCollapseFn } from '../../../utils'; export const getConfigurationForMetric = ( model: Panel, @@ -37,7 +21,6 @@ export const getConfigurationForMetric = ( const [primarySeries, secondarySeries] = model.series.filter(({ hidden }) => !hidden); const primaryMetricWithCollapseFn = getMetricWithCollapseFn(primarySeries); - if (!primaryMetricWithCollapseFn || !primaryMetricWithCollapseFn.metric) { return null; } @@ -45,7 +28,6 @@ export const getConfigurationForMetric = ( const secondaryMetricWithCollapseFn = getMetricWithCollapseFn(secondarySeries); const primaryColumn = findMetricColumn(primaryMetricWithCollapseFn.metric, layer.columns); const secondaryColumn = findMetricColumn(secondaryMetricWithCollapseFn?.metric, layer.columns); - if (primaryMetricWithCollapseFn.collapseFn && secondaryMetricWithCollapseFn?.collapseFn) { return null; } @@ -65,3 +47,35 @@ export const getConfigurationForMetric = ( collapseFn: primaryMetricWithCollapseFn.collapseFn ?? secondaryMetricWithCollapseFn?.collapseFn, }; }; + +export const getConfigurationForGauge = ( + model: Panel, + layer: Layer, + bucket: Column | undefined, + gaugeMaxColumn: Column +): MetricVisConfiguration | null => { + const primarySeries = model.series[0]; + const primaryMetricWithCollapseFn = getMetricWithCollapseFn(primarySeries); + if (!primaryMetricWithCollapseFn || !primaryMetricWithCollapseFn.metric) { + return null; + } + + const primaryColumn = findMetricColumn(primaryMetricWithCollapseFn.metric, layer.columns); + const primaryColor = primarySeries.color ? color(primarySeries.color).hex() : undefined; + + const gaugePalette = getPalette(model.gauge_color_rules ?? [], primaryColor); + if (gaugePalette === null) { + return null; + } + + return { + layerId: layer.layerId, + layerType: 'data', + metricAccessor: primaryColumn?.columnId, + breakdownByAccessor: bucket?.columnId, + maxAccessor: gaugeMaxColumn.columnId, + palette: gaugePalette, + collapseFn: primaryMetricWithCollapseFn.collapseFn, + ...(gaugePalette ? {} : { color: primaryColor }), + }; +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts index 827dc15ff171..b7356f094f91 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts @@ -9,6 +9,7 @@ import { getPalette } from './palette'; describe('getPalette', () => { + const baseColor = '#fff'; const invalidRules = [ { id: 'some-id-0' }, { id: 'some-id-1', value: 10 }, @@ -16,162 +17,346 @@ describe('getPalette', () => { { id: 'some-id-3', color: '#000' }, { id: 'some-id-4', background_color: '#000' }, ]; - test('should return undefined if no filled rules was provided', () => { - expect(getPalette([])).toBeUndefined(); - expect(getPalette(invalidRules)).toBeUndefined(); - }); - test('should return undefined if only one valid rule is provided and it is not lte', () => { - expect(getPalette([])).toBeUndefined(); - expect( - getPalette([ - ...invalidRules, - { id: 'some-id-5', operator: 'gt', value: 100, background_color: '#000' }, - ]) - ).toBeUndefined(); - }); + describe('Metric', () => { + test('should return undefined if no filled rules was provided', () => { + expect(getPalette([])).toBeUndefined(); + expect(getPalette(invalidRules)).toBeUndefined(); + }); + + test('should return undefined if only one valid rule is provided and it is not lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'gt', value: 100, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); - test('should return custom palette if only one valid rule is provided and it is lte', () => { - expect(getPalette([])).toBeUndefined(); - expect( - getPalette([ - ...invalidRules, - { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, - ]) - ).toEqual({ - name: 'custom', - params: { - colorStops: [{ color: '#000000', stop: 100 }], - continuity: 'below', - maxSteps: 5, + test('should return custom palette if only one valid rule is provided and it is lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, + ]) + ).toEqual({ name: 'custom', - progression: 'fixed', - rangeMax: 100, - rangeMin: -Infinity, - rangeType: 'number', - reverse: false, - steps: 1, - stops: [{ color: '#000000', stop: 100 }], - }, - type: 'palette', + params: { + colorStops: [{ color: '#000000', stop: 100 }], + continuity: 'below', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 100, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + steps: 1, + stops: [{ color: '#000000', stop: 100 }], + }, + type: 'palette', + }); }); - }); - test('should return undefined if more than two types of rules', () => { - expect(getPalette([])).toBeUndefined(); - expect( - getPalette([ - ...invalidRules, - { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, - { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, - { id: 'some-id-7', operator: 'lt', value: 200, background_color: '#000' }, - ]) - ).toBeUndefined(); - }); + test('should return undefined if more than two types of rules', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, + { id: 'some-id-7', operator: 'lt', value: 200, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); - test('should return undefined if two types of rules and last rule is not lte', () => { - expect(getPalette([])).toBeUndefined(); - expect( - getPalette([ - ...invalidRules, - { id: 'some-id-5', operator: 'gte', value: 100, background_color: '#000' }, - { id: 'some-id-7', operator: 'lt', value: 200, background_color: '#000' }, - { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, - ]) - ).toBeUndefined(); - }); + test('should return undefined if two types of rules and last rule is not lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'gte', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'lt', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); - test('should return undefined if all rules are lte', () => { - expect(getPalette([])).toBeUndefined(); - expect( - getPalette([ - ...invalidRules, - { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, - { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, - { id: 'some-id-6', operator: 'lte', value: 150, background_color: '#000' }, - ]) - ).toBeUndefined(); - }); + test('should return undefined if all rules are lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lte', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'lte', value: 150, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); - test('should return undefined if two types of rules and all except last one are lt and last one is not lte', () => { - expect(getPalette([])).toBeUndefined(); - expect( - getPalette([ - ...invalidRules, - { id: 'some-id-5', operator: 'lt', value: 100, background_color: '#000' }, - { id: 'some-id-7', operator: 'gte', value: 200, background_color: '#000' }, - { id: 'some-id-6', operator: 'lt', value: 150, background_color: '#000' }, - ]) - ).toBeUndefined(); - }); + test('should return undefined if two types of rules and all except last one are lt and last one is not lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lt', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'gte', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'lt', value: 150, background_color: '#000' }, + ]) + ).toBeUndefined(); + }); - test('should return custom palette if two types of rules and all except last one is lt and last one is lte', () => { - expect(getPalette([])).toBeUndefined(); - expect( - getPalette([ - ...invalidRules, - { id: 'some-id-5', operator: 'lt', value: 100, background_color: '#000' }, - { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, - { id: 'some-id-6', operator: 'lt', value: 150, background_color: '#000' }, - ]) - ).toEqual({ - name: 'custom', - params: { - colorStops: [ - { color: '#000000', stop: -Infinity }, - { color: '#000000', stop: 100 }, - { color: '#000000', stop: 150 }, - ], - continuity: 'below', - maxSteps: 5, + test('should return custom palette if two types of rules and all except last one is lt and last one is lte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'lt', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'lt', value: 150, background_color: '#000' }, + ]) + ).toEqual({ name: 'custom', - progression: 'fixed', - rangeMax: 200, - rangeMin: -Infinity, - rangeType: 'number', - reverse: false, - steps: 4, - stops: [ - { color: '#000000', stop: 100 }, - { color: '#000000', stop: 150 }, - { color: '#000000', stop: 200 }, - ], - }, - type: 'palette', + params: { + colorStops: [ + { color: '#000000', stop: -Infinity }, + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + ], + continuity: 'below', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 200, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + steps: 4, + stops: [ + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + { color: '#000000', stop: 200 }, + ], + }, + type: 'palette', + }); + }); + + test('should return custom palette if last one is lte and all previous are gte', () => { + expect(getPalette([])).toBeUndefined(); + expect( + getPalette([ + ...invalidRules, + { id: 'some-id-5', operator: 'gte', value: 100, background_color: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, + ]) + ).toEqual({ + name: 'custom', + params: { + colorStops: [ + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + ], + continuity: 'none', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 200, + rangeMin: 100, + rangeType: 'number', + reverse: false, + steps: 2, + stops: [ + { color: '#000000', stop: 150 }, + { color: '#000000', stop: 200 }, + ], + }, + type: 'palette', + }); }); }); - test('should return custom palette if last one is lte and all previous are gte', () => { - expect(getPalette([])).toBeUndefined(); - expect( - getPalette([ - ...invalidRules, - { id: 'some-id-5', operator: 'gte', value: 100, background_color: '#000' }, - { id: 'some-id-7', operator: 'lte', value: 200, background_color: '#000' }, - { id: 'some-id-6', operator: 'gte', value: 150, background_color: '#000' }, - ]) - ).toEqual({ - name: 'custom', - params: { - colorStops: [ - { color: '#000000', stop: 100 }, - { color: '#000000', stop: 150 }, - ], - continuity: 'none', - maxSteps: 5, + describe('Gauge', () => { + test('should return undefined if no filled rules was provided', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect(getPalette(invalidRules, baseColor)).toBeUndefined(); + }); + + test('should return undefined if only one valid rule is provided and it is not lte', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect( + getPalette( + [...invalidRules, { id: 'some-id-5', operator: 'gt', value: 100, gauge: '#000' }], + baseColor + ) + ).toBeUndefined(); + }); + + test('should return custom palette if only one valid rule is provided and it is lte', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect( + getPalette( + [...invalidRules, { id: 'some-id-5', operator: 'lte', value: 100, gauge: '#000' }], + baseColor + ) + ).toEqual({ + name: 'custom', + params: { + colorStops: [{ color: '#000000', stop: 100 }], + continuity: 'below', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 100, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + steps: 1, + stops: [{ color: '#000000', stop: 100 }], + }, + type: 'palette', + }); + }); + + test('should return undefined if more than two types of rules', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect( + getPalette( + [ + ...invalidRules, + { id: 'some-id-5', operator: 'lte', value: 100, gauge: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, gauge: '#000' }, + { id: 'some-id-7', operator: 'lt', value: 200, gauge: '#000' }, + ], + baseColor + ) + ).toBeUndefined(); + }); + + test('should return undefined if two types of rules and last rule is not lte', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect( + getPalette( + [ + ...invalidRules, + { id: 'some-id-5', operator: 'gte', value: 100, gauge: '#000' }, + { id: 'some-id-7', operator: 'lt', value: 200, gauge: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, gauge: '#000' }, + ], + baseColor + ) + ).toBeUndefined(); + }); + + test('should return undefined if all rules are lte', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect( + getPalette( + [ + ...invalidRules, + { id: 'some-id-5', operator: 'lte', value: 100, gauge: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, gauge: '#000' }, + { id: 'some-id-6', operator: 'lte', value: 150, gauge: '#000' }, + ], + baseColor + ) + ).toBeUndefined(); + }); + + test('should return undefined if two types of rules and all except last one are lt and last one is not lte', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect( + getPalette( + [ + ...invalidRules, + { id: 'some-id-5', operator: 'lt', value: 100, gauge: '#000' }, + { id: 'some-id-7', operator: 'gte', value: 200, gauge: '#000' }, + { id: 'some-id-6', operator: 'lt', value: 150, gauge: '#000' }, + ], + baseColor + ) + ).toBeUndefined(); + }); + + test('should return custom palette if two types of rules and all except last one is lt and last one is lte', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect( + getPalette( + [ + ...invalidRules, + { id: 'some-id-5', operator: 'lt', value: 100, gauge: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, gauge: '#000' }, + { id: 'some-id-6', operator: 'lt', value: 150, gauge: '#000' }, + ], + baseColor + ) + ).toEqual({ + name: 'custom', + params: { + colorStops: [ + { color: '#000000', stop: -Infinity }, + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + ], + continuity: 'below', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 200, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + steps: 4, + stops: [ + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + { color: '#000000', stop: 200 }, + ], + }, + type: 'palette', + }); + }); + + test('should return custom palette if last one is lte and all previous are gte', () => { + expect(getPalette([], baseColor)).toBeUndefined(); + expect( + getPalette( + [ + ...invalidRules, + { id: 'some-id-5', operator: 'gte', value: 100, gauge: '#000' }, + { id: 'some-id-7', operator: 'lte', value: 200, gauge: '#000' }, + { id: 'some-id-6', operator: 'gte', value: 150, gauge: '#000' }, + ], + baseColor + ) + ).toEqual({ name: 'custom', - progression: 'fixed', - rangeMax: 200, - rangeMin: 100, - rangeType: 'number', - reverse: false, - steps: 2, - stops: [ - { color: '#000000', stop: 150 }, - { color: '#000000', stop: 200 }, - ], - }, - type: 'palette', + params: { + colorStops: [ + { color: baseColor, stop: -Infinity }, + { color: '#000000', stop: 100 }, + { color: '#000000', stop: 150 }, + ], + continuity: 'below', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 200, + rangeMin: -Infinity, + rangeType: 'number', + reverse: false, + steps: 3, + stops: [ + { color: baseColor, stop: 100 }, + { color: '#000000', stop: 150 }, + { color: '#000000', stop: 200 }, + ], + }, + type: 'palette', + }); }); }); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts index 55741c57595e..4079ffb39664 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts @@ -22,15 +22,61 @@ type ColorStopsWithMinMax = Pick< 'colorStops' | 'stops' | 'steps' | 'rangeMax' | 'rangeMin' | 'continuity' >; +type MetricColorRules = Exclude; +type GaugeColorRules = Exclude; + +type MetricColorRule = MetricColorRules[number]; +type GaugeColorRule = GaugeColorRules[number]; + +type ValidMetricColorRule = Omit & + ( + | { + background_color: Exclude; + color: MetricColorRule['color']; + } + | { + background_color: MetricColorRule['background_color']; + color: Exclude; + } + ); + +type ValidGaugeColorRule = Omit & { + gauge: Exclude; +}; + +const isValidColorRule = ( + rule: MetricColorRule | GaugeColorRule +): rule is ValidMetricColorRule | ValidGaugeColorRule => { + const { background_color: bColor, color: textColor } = rule as MetricColorRule; + const { gauge } = rule as GaugeColorRule; + + return rule.operator && (bColor ?? textColor ?? gauge) && rule.value !== undefined ? true : false; +}; + +const isMetricColorRule = ( + rule: ValidMetricColorRule | ValidGaugeColorRule +): rule is ValidMetricColorRule => { + const metricRule = rule as ValidMetricColorRule; + return metricRule.background_color ?? metricRule.color ? true : false; +}; + +const getColor = (rule: ValidMetricColorRule | ValidGaugeColorRule) => { + if (isMetricColorRule(rule)) { + return rule.background_color ?? rule.color; + } + return rule.gauge; +}; + const getColorStopsWithMinMaxForAllGteOrWithLte = ( - rules: Exclude, - tailOperator: string + rules: Array, + tailOperator: string, + baseColor?: string ): ColorStopsWithMinMax => { const lastRule = rules[rules.length - 1]; - const lastRuleColor = (lastRule.background_color ?? lastRule.color)!; - + const lastRuleColor = getColor(lastRule); + const initRules = baseColor ? [{ stop: -Infinity, color: baseColor }] : []; const colorStops = rules.reduce((colors, rule, index, rulesArr) => { - const rgbColor = (rule.background_color ?? rule.color)!; + const rgbColor = getColor(rule); if (index === rulesArr.length - 1 && tailOperator === Operators.LTE) { return colors; } @@ -51,7 +97,7 @@ const getColorStopsWithMinMaxForAllGteOrWithLte = ( stop: rule.value!, }, ]; - }, []); + }, initRules); const stops = colorStops.reduce((prevStops, colorStop, index, colorStopsArr) => { if (index === colorStopsArr.length - 1) { @@ -68,24 +114,25 @@ const getColorStopsWithMinMaxForAllGteOrWithLte = ( const [rule] = rules; return { - rangeMin: rule.value, + rangeMin: baseColor ? -Infinity : rule.value, rangeMax: tailOperator === Operators.LTE ? lastRule.value : Infinity, colorStops, stops, steps: colorStops.length, - continuity: tailOperator === Operators.LTE ? 'none' : 'above', + continuity: + tailOperator === Operators.LTE ? (baseColor ? 'below' : 'none') : baseColor ? 'all' : 'above', }; }; const getColorStopsWithMinMaxForLtWithLte = ( - rules: Exclude + rules: Array ): ColorStopsWithMinMax => { const lastRule = rules[rules.length - 1]; const colorStops = rules.reduce((colors, rule, index, rulesArr) => { if (index === 0) { - return [{ color: color((rule.background_color ?? rule.color)!).hex(), stop: -Infinity }]; + return [{ color: color(getColor(rule)).hex(), stop: -Infinity }]; } - const rgbColor = (rule.background_color ?? rule.color)!; + const rgbColor = getColor(rule); return [ ...colors, { @@ -119,10 +166,10 @@ const getColorStopsWithMinMaxForLtWithLte = ( }; const getColorStopWithMinMaxForLte = ( - rule: Exclude[number] + rule: ValidMetricColorRule | ValidGaugeColorRule ): ColorStopsWithMinMax => { const colorStop = { - color: color((rule.background_color ?? rule.color)!).hex(), + color: color(getColor(rule)).hex(), stop: rule.value!, }; return { @@ -135,6 +182,27 @@ const getColorStopWithMinMaxForLte = ( }; }; +const getColorStopWithMinMaxForGte = ( + rule: ValidMetricColorRule | ValidGaugeColorRule, + baseColor?: string +): ColorStopsWithMinMax => { + const colorStop = { + color: color(getColor(rule)).hex(), + stop: rule.value!, + }; + return { + colorStops: [...(baseColor ? [{ color: baseColor, stop: -Infinity }] : []), colorStop], + continuity: baseColor ? 'all' : 'above', + rangeMax: Infinity, + rangeMin: baseColor ? -Infinity : colorStop.stop, + steps: 2, + stops: [ + ...(baseColor ? [{ color: baseColor, stop: colorStop.stop }] : []), + { color: colorStop.color, stop: Infinity }, + ], + }; +}; + const getCustomPalette = ( colorStopsWithMinMax: ColorStopsWithMinMax ): PaletteOutput => { @@ -156,13 +224,12 @@ const getCustomPalette = ( }; export const getPalette = ( - rules: Exclude + rules: MetricColorRules | GaugeColorRules, + baseColor?: string ): PaletteOutput | null | undefined => { - const validRules = - rules.filter( - ({ operator, color: textColor, value, background_color: bColor }) => - operator && (bColor ?? textColor) && value !== undefined - ) ?? []; + const validRules = (rules as Array).filter< + ValidMetricColorRule | ValidGaugeColorRule + >((rule): rule is ValidMetricColorRule | ValidGaugeColorRule => isValidColorRule(rule)); validRules.sort((rule1, rule2) => { return rule1.value! - rule2.value!; @@ -176,10 +243,14 @@ export const getPalette = ( // lnsMetric is supporting lte only, if one rule is defined if (validRules.length === 1) { - if (validRules[0].operator !== Operators.LTE) { - return; + if (validRules[0].operator === Operators.LTE) { + return getCustomPalette(getColorStopWithMinMaxForLte(validRules[0])); } - return getCustomPalette(getColorStopWithMinMaxForLte(validRules[0])); + + if (validRules[0].operator === Operators.GTE) { + return getCustomPalette(getColorStopWithMinMaxForGte(validRules[0], baseColor)); + } + return; } const headRules = validRules.slice(0, -1); @@ -208,7 +279,7 @@ export const getPalette = ( if (rule.operator === Operators.GTE) { return getCustomPalette( - getColorStopsWithMinMaxForAllGteOrWithLte(validRules, tailRule.operator!) + getColorStopsWithMinMaxForAllGteOrWithLte(validRules, tailRule.operator!, baseColor) ); } }; 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 46e9d9e1fae2..3bb4b743c258 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 @@ -110,7 +110,6 @@ describe('getLayers', () => { params: { value: '100', }, - meta: { metricId: 'metric-1' }, }, ], columnOrder: [], diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/formula.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/formula.ts index 730e770160ec..15b35fade92c 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/formula.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/formula.ts @@ -6,8 +6,12 @@ * Side Public License, v 1. */ +import uuid from 'uuid'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; -import { FormulaParams } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { + FormulaParams, + FormulaColumn as BaseFormulaColumn, +} from '@kbn/visualizations-plugin/common/convert_to_lens'; import { CommonColumnConverterArgs, CommonColumnsConverterArgs, FormulaColumn } from './types'; import { TSVB_METRIC_TYPES } from '../../../../common/enums'; import type { Metric } from '../../../../common/types'; @@ -39,6 +43,19 @@ export const createFormulaColumn = ( }; }; +export const createFormulaColumnWithoutMeta = (formula: string): BaseFormulaColumn => { + const params = convertToFormulaParams(formula); + return { + columnId: uuid(), + operationType: 'formula', + references: [], + dataType: 'string', + isSplit: false, + isBucketed: false, + params: { ...params }, + }; +}; + const convertFormulaScriptForPercentileAggs = ( mathScript: string, variables: Exclude, diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts index e03701e6ea15..b1b15339b37a 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts @@ -9,7 +9,11 @@ export { isColumnWithMeta, excludeMetaFromColumn } from './column'; export { convertToPercentileColumns, isPercentileColumnWithMeta } from './percentile'; export { convertToPercentileRankColumns, isPercentileRanksColumnWithMeta } from './percentile_rank'; -export { convertMathToFormulaColumn, convertOtherAggsToFormulaColumn } from './formula'; +export { + convertMathToFormulaColumn, + convertOtherAggsToFormulaColumn, + createFormulaColumnWithoutMeta, +} from './formula'; export { convertParentPipelineAggToColumns, convertMetricAggregationColumnWithoutSpecialParams, @@ -17,7 +21,11 @@ export { export { convertToCumulativeSumColumns } from './cumulative_sum'; export { convertFilterRatioToFormulaColumn } from './filter_ratio'; export { convertToLastValueColumn } from './last_value'; -export { convertToStaticValueColumn, convertStaticValueToFormulaColumn } from './static_value'; +export { + convertToStaticValueColumn, + createStaticValueColumn, + convertStaticValueToFormulaColumn, +} from './static_value'; export { convertToFiltersColumn } from './filters'; export { convertToDateHistogramColumn } from './date_histogram'; export { convertToTermsColumn } from './terms'; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts index e03a9d782136..d3e6aef09b1c 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts @@ -6,7 +6,11 @@ * Side Public License, v 1. */ -import { StaticValueParams } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import uuid from 'uuid'; +import { + StaticValueParams, + StaticValueColumn as BaseStaticValueColumn, +} from '@kbn/visualizations-plugin/common/convert_to_lens'; import { CommonColumnsConverterArgs, FormulaColumn, StaticValueColumn } from './types'; import type { Metric } from '../../../../common/types'; import { createColumn, getFormat } from './column'; @@ -39,6 +43,19 @@ export const convertToStaticValueColumn = ( }; }; +export const createStaticValueColumn = (staticValue: number): BaseStaticValueColumn => ({ + columnId: uuid(), + operationType: 'static_value', + references: [], + dataType: 'number', + isStaticValue: true, + isBucketed: false, + isSplit: false, + params: { + value: staticValue.toString(), + }, +}); + export const convertStaticValueToFormulaColumn = ( { series, metrics, dataView }: CommonColumnsConverterArgs, { diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts index e5b862a0fe70..4b3b6c582f91 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts @@ -85,7 +85,13 @@ export type MovingAverageColumn = GenericColumnWithMeta; export type StaticValueColumn = GenericColumnWithMeta; -export type ColumnsWithoutMeta = FiltersColumn | TermsColumn | DateHistogramColumn; +export type ColumnsWithoutMeta = + | FiltersColumn + | TermsColumn + | DateHistogramColumn + | BaseStaticValueColumn + | BaseFormulaColumn; + export type AnyColumnWithReferences = GenericColumnWithMeta; type CommonColumns = Exclude; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts index 76d15793f451..8be4b444be09 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts @@ -66,6 +66,7 @@ const supportedPanelTypes: readonly PANEL_TYPES[] = [ PANEL_TYPES.TIMESERIES, PANEL_TYPES.TOP_N, PANEL_TYPES.METRIC, + PANEL_TYPES.GAUGE, ]; const supportedTimeRangeModes: readonly TIME_RANGE_DATA_MODES[] = [ diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts index 25f55b5a1c44..149acc513b9f 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts @@ -17,7 +17,7 @@ import { getConfigurationForMetric as getConfiguration } from '../lib/configurat import { getReducedTimeRange, isValidMetrics } from '../lib/metrics'; import { ConvertTsvbToLensVisualization } from '../types'; import { ColumnsWithoutMeta, Layer as ExtendedLayer } from '../lib/convert'; -import { excludeMetaFromLayers, getUniqueBuckets } from './utils'; +import { excludeMetaFromLayers, getUniqueBuckets } from '../utils'; const MAX_SERIES = 2; const MAX_BUCKETS = 2; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/utils.test.ts similarity index 97% rename from src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.test.ts rename to src/plugins/vis_types/timeseries/public/convert_to_lens/utils.test.ts index 8f880dfe95c5..c60370871294 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Column, DateHistogramColumn, TermsColumn } from '../lib/convert'; +import { Column, DateHistogramColumn, TermsColumn } from './lib/convert'; import { getUniqueBuckets } from './utils'; describe('getUniqueBuckets', () => { diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/utils.ts similarity index 75% rename from src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.ts rename to src/plugins/vis_types/timeseries/public/convert_to_lens/utils.ts index 8df1b0f40f8b..93fb56e39012 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/utils.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/utils.ts @@ -9,7 +9,14 @@ import { uniqWith } from 'lodash'; import deepEqual from 'react-fast-compare'; import { Layer, Operations, TermsColumn } from '@kbn/visualizations-plugin/common/convert_to_lens'; -import { Layer as ExtendedLayer, excludeMetaFromColumn, ColumnsWithoutMeta } from '../lib/convert'; +import { + Layer as ExtendedLayer, + excludeMetaFromColumn, + ColumnsWithoutMeta, + Column, +} from './lib/convert'; +import { getSeriesAgg } from './lib/series'; +import { Metric, Series } from '../../common/types'; export const excludeMetaFromLayers = ( layers: Record @@ -63,3 +70,20 @@ export const getUniqueBuckets = (buckets: ColumnsWithoutMeta[]) => return deepEqual(bucketWithoutColumnIds1, bucketWithoutColumnIds2); }); + +export const getMetricWithCollapseFn = (series: Series | undefined) => { + if (!series) { + return; + } + const { metrics, seriesAgg } = getSeriesAgg(series.metrics); + const visibleMetric = metrics[metrics.length - 1]; + return { metric: visibleMetric, collapseFn: seriesAgg }; +}; + +export const findMetricColumn = (metric: Metric | undefined, columns: Column[]) => { + if (!metric) { + return; + } + + return columns.find((column) => 'meta' in column && column.meta.metricId === metric.id); +}; diff --git a/src/plugins/vis_types/timeseries/public/request_handler.ts b/src/plugins/vis_types/timeseries/public/request_handler.ts index 24768dc10a17..4512034d3b12 100644 --- a/src/plugins/vis_types/timeseries/public/request_handler.ts +++ b/src/plugins/vis_types/timeseries/public/request_handler.ts @@ -49,33 +49,46 @@ export const metricsRequestHandler = async ({ const dataSearch = data.search; const parsedTimeRange = data.query.timefilter.timefilter.calculateBounds(input?.timeRange!); + const doSearch = async ( + searchOptions: ReturnType + ): Promise => { + return await getCoreStart().http.post(ROUTES.VIS_DATA, { + body: JSON.stringify({ + timerange: { + timezone, + ...parsedTimeRange, + }, + query: input?.query, + filters: input?.filters, + panels: [visParams], + state: uiStateObj, + ...(searchOptions + ? { + searchSession: searchOptions, + } + : {}), + }), + context: executionContext, + signal: abortController.signal, + }); + }; + if (visParams && visParams.id && !visParams.isModelInvalid && !expressionAbortSignal.aborted) { - const untrackSearch = - dataSearch.session.isCurrentSession(searchSessionId) && - dataSearch.session.trackSearch({ - abort: () => abortController.abort(), - }); + const searchTracker = dataSearch.session.isCurrentSession(searchSessionId) + ? dataSearch.session.trackSearch({ + abort: () => abortController.abort(), + poll: async () => { + // don't use, keep this empty, onSavingSession is used instead + }, + onSavingSession: async (searchSessionOptions) => { + await doSearch(searchSessionOptions); + }, + }) + : undefined; try { const searchSessionOptions = dataSearch.session.getSearchOptions(searchSessionId); - - const visData: TimeseriesVisData = await getCoreStart().http.post(ROUTES.VIS_DATA, { - body: JSON.stringify({ - timerange: { - timezone, - ...parsedTimeRange, - }, - query: input?.query, - filters: input?.filters, - panels: [visParams], - state: uiStateObj, - ...(searchSessionOptions && { - searchSession: searchSessionOptions, - }), - }), - context: executionContext, - signal: abortController.signal, - }); + const visData: TimeseriesVisData = await doSearch(searchSessionOptions); inspectorAdapters?.requests?.reset(); @@ -86,12 +99,12 @@ export const metricsRequestHandler = async ({ .ok({ time: query.time, json: { rawResponse: query.response } }); }); + searchTracker?.complete(); + return visData; + } catch (e) { + searchTracker?.error(); } finally { - if (untrackSearch && dataSearch.session.isCurrentSession(searchSessionId)) { - // untrack if this search still belongs to current session - untrackSearch(); - } expressionAbortSignal.removeEventListener('abort', expressionAbortHandler); } } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.ts index b1ac46dd36a2..7fe98b9f7ec6 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/column.ts @@ -25,7 +25,7 @@ export const createColumn = ( { isBucketed = false, isSplit = false, reducedTimeRange }: ExtraColumnFields = {} ): GeneralColumnWithMeta => ({ columnId: uuid(), - dataType: (field?.type as DataType) ?? undefined, + dataType: (field?.type as DataType) ?? 'number', label: getLabel(agg), isBucketed, isSplit, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/index.ts index b77ea6b97209..9da33607be2a 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/index.ts @@ -20,3 +20,4 @@ export * from './terms'; export * from './types'; export * from './last_value'; export * from './range'; +export * from './percentage_mode'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts new file mode 100644 index 000000000000..3b7e8ad7e797 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { SchemaConfig } from '../../..'; +import { convertToColumnInPercentageMode } from './percentage_mode'; + +const mockGetFormulaForAgg = jest.fn(); + +jest.mock('../metrics/formula', () => ({ + getFormulaForAgg: jest.fn(() => mockGetFormulaForAgg()), +})); + +describe('convertToColumnInPercentageMode', () => { + const formula = 'average(some_field)'; + const dataView = stubLogstashDataView; + + const agg: SchemaConfig = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + aggType: METRIC_TYPES.AVG, + aggParams: { + field: dataView.fields[0].displayName, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockGetFormulaForAgg.mockReturnValue(formula); + }); + + test('should return null if it is not possible to build the valid formula', () => { + mockGetFormulaForAgg.mockReturnValue(null); + expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, {})).toBeNull(); + }); + + test('should return percentage mode over range formula if min and max was passed', () => { + const formulaColumn = { + operationType: 'formula', + params: { format: { id: 'percent' }, formula: `((${formula}) - 0) / (100 - 0)` }, + }; + expect( + convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, { min: 0, max: 100 }) + ).toEqual(expect.objectContaining(formulaColumn)); + }); + + test('should return percentage mode formula if min and max was not passed', () => { + const formulaColumn = { + operationType: 'formula', + params: { format: { id: 'percent' }, formula: `(${formula}) / 10000` }, + }; + expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, {})).toEqual( + expect.objectContaining(formulaColumn) + ); + }); +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.ts new file mode 100644 index 000000000000..df49635c9763 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METRIC_TYPES } from '@kbn/data-plugin/common'; +import { MinMax } from '../../types'; +import { getFormulaForAgg } from '../metrics/formula'; +import { createFormulaColumn } from './formula'; +import { ExtendedColumnConverterArgs } from './types'; + +const getPercentageFormulaOverRange = (formula: string, { min, max }: MinMax) => + `((${formula}) - ${min}) / (${max} - ${min})`; + +// Lens is multiplying by 100, so, it is necessary to disable that operation. +const getPercentageFormula = (formula: string) => `(${formula}) / 10000`; + +const isMinMax = (minMax: MinMax | {}): minMax is MinMax => { + if ((minMax as MinMax).min !== undefined && (minMax as MinMax).max !== undefined) { + return true; + } + return false; +}; + +export const convertToColumnInPercentageMode = ( + columnConverterArgs: ExtendedColumnConverterArgs, + minMax: MinMax | {} +) => { + if (columnConverterArgs.agg.aggType === METRIC_TYPES.TOP_HITS) { + return null; + } + + const formula = getFormulaForAgg(columnConverterArgs); + if (formula === null) { + return null; + } + + const percentageModeFormula = isMinMax(minMax) + ? getPercentageFormulaOverRange(formula, minMax) + : getPercentageFormula(formula); + + const column = createFormulaColumn(percentageModeFormula, columnConverterArgs.agg); + if (column === null) { + return null; + } + return { + ...column, + params: { ...column?.params, format: { id: 'percent' } }, + }; +}; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts index 659ab80ea03d..207dedd133bc 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts @@ -19,6 +19,7 @@ const mockConvertToSiblingPipelineColumns = jest.fn(); const mockConvertToStdDeviationFormulaColumns = jest.fn(); const mockConvertToLastValueColumn = jest.fn(); const mockConvertToCumulativeSumAggColumn = jest.fn(); +const mockConvertToColumnInPercentageMode = jest.fn(); jest.mock('../convert', () => ({ convertMetricAggregationColumnWithoutSpecialParams: jest.fn(() => @@ -33,6 +34,7 @@ jest.mock('../convert', () => ({ convertToStdDeviationFormulaColumns: jest.fn(() => mockConvertToStdDeviationFormulaColumns()), convertToLastValueColumn: jest.fn(() => mockConvertToLastValueColumn()), convertToCumulativeSumAggColumn: jest.fn(() => mockConvertToCumulativeSumAggColumn()), + convertToColumnInPercentageMode: jest.fn(() => mockConvertToColumnInPercentageMode()), })); describe('convertMetricToColumns invalid cases', () => { @@ -56,145 +58,265 @@ describe('convertMetricToColumns invalid cases', () => { test.each<[string, Parameters, null, jest.Mock | undefined]>([ [ 'null if agg is not supported', - [{ aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, undefined, ], [ 'null if supported agg AVG is not valid', - [{ aggType: METRIC_TYPES.AVG } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.AVG } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'null if supported agg MIN is not valid', - [{ aggType: METRIC_TYPES.MIN } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MIN } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'null if supported agg MAX is not valid', - [{ aggType: METRIC_TYPES.MAX } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MAX } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'null if supported agg SUM is not valid', - [{ aggType: METRIC_TYPES.SUM } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SUM } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'null if supported agg COUNT is not valid', - [{ aggType: METRIC_TYPES.COUNT } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.COUNT } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'null if supported agg CARDINALITY is not valid', - [{ aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'null if supported agg VALUE_COUNT is not valid', - [{ aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'null if supported agg MEDIAN is not valid', - [{ aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'null if supported agg STD_DEV is not valid', - [{ aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToStdDeviationFormulaColumns, ], [ 'null if supported agg PERCENTILES is not valid', - [{ aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToPercentileColumn, ], [ 'null if supported agg SINGLE_PERCENTILE is not valid', - [{ aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToPercentileColumn, ], [ 'null if supported agg PERCENTILE_RANKS is not valid', - [{ aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToPercentileRankColumn, ], [ 'null if supported agg SINGLE_PERCENTILE_RANK is not valid', - [{ aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToPercentileRankColumn, ], [ 'null if supported agg TOP_HITS is not valid', - [{ aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToLastValueColumn, ], [ 'null if supported agg TOP_METRICS is not valid', - [{ aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToLastValueColumn, ], [ 'null if supported agg CUMULATIVE_SUM is not valid', - [{ aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToCumulativeSumAggColumn, ], [ 'null if supported agg DERIVATIVE is not valid', - [{ aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToOtherParentPipelineAggColumns, ], [ 'null if supported agg MOVING_FN is not valid', - [{ aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToOtherParentPipelineAggColumns, ], [ 'null if supported agg SUM_BUCKET is not valid', - [{ aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToSiblingPipelineColumns, ], [ 'null if supported agg MIN_BUCKET is not valid', - [{ aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToSiblingPipelineColumns, ], [ 'null if supported agg MAX_BUCKET is not valid', - [{ aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToSiblingPipelineColumns, ], [ 'null if supported agg AVG_BUCKET is not valid', - [{ aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, mockConvertToSiblingPipelineColumns, ], [ 'null if supported agg SERIAL_DIFF is not valid', - [{ aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], null, undefined, ], @@ -224,141 +346,274 @@ describe('convertMetricToColumns valid cases', () => { mockConvertToStdDeviationFormulaColumns.mockReturnValue(result); mockConvertToLastValueColumn.mockReturnValue(result); mockConvertToCumulativeSumAggColumn.mockReturnValue(result); + mockConvertToColumnInPercentageMode.mockReturnValue(result); }); test.each<[string, Parameters, Array<{}>, jest.Mock]>([ [ 'array of columns if supported agg AVG is valid', - [{ aggType: METRIC_TYPES.AVG } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.AVG } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'array of columns if supported agg MIN is valid', - [{ aggType: METRIC_TYPES.MIN } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MIN } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'array of columns if supported agg MAX is valid', - [{ aggType: METRIC_TYPES.MAX } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MAX } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'array of columns if supported agg SUM is valid', - [{ aggType: METRIC_TYPES.SUM } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SUM } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'array of columns if supported agg COUNT is valid', - [{ aggType: METRIC_TYPES.COUNT } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.COUNT } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'array of columns if supported agg CARDINALITY is valid', - [{ aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'array of columns if supported agg VALUE_COUNT is valid', - [{ aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'array of columns if supported agg MEDIAN is valid', - [{ aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertMetricAggregationColumnWithoutSpecialParams, ], [ 'array of columns if supported agg STD_DEV is valid', - [{ aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToStdDeviationFormulaColumns, ], [ 'array of columns if supported agg PERCENTILES is valid', - [{ aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToPercentileColumn, ], [ 'array of columns if supported agg SINGLE_PERCENTILE is valid', - [{ aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToPercentileColumn, ], [ 'array of columns if supported agg PERCENTILE_RANKS is valid', - [{ aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToPercentileRankColumn, ], [ 'array of columns if supported agg SINGLE_PERCENTILE_RANK is valid', - [{ aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToPercentileRankColumn, ], [ 'array of columns if supported agg TOP_HITS is valid', - [{ aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToLastValueColumn, ], [ 'array of columns if supported agg TOP_METRICS is valid', - [{ aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToLastValueColumn, ], [ 'array of columns if supported agg CUMULATIVE_SUM is valid', - [{ aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToCumulativeSumAggColumn, ], [ 'array of columns if supported agg DERIVATIVE is valid', - [{ aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToOtherParentPipelineAggColumns, ], [ 'array of columns if supported agg MOVING_FN is valid', - [{ aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToOtherParentPipelineAggColumns, ], [ 'array of columns if supported agg SUM_BUCKET is valid', - [{ aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToSiblingPipelineColumns, ], [ 'array of columns if supported agg MIN_BUCKET is valid', - [{ aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToSiblingPipelineColumns, ], [ 'array of columns if supported agg MAX_BUCKET is valid', - [{ aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToSiblingPipelineColumns, ], [ 'array of columns if supported agg AVG_BUCKET is valid', - [{ aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, dataView, []], + [ + { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: false }, + ], result, mockConvertToSiblingPipelineColumns, ], + [ + 'column in percentage mode without range if percentageMode is enabled ', + [ + { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: true }, + ], + result, + mockConvertToColumnInPercentageMode, + ], + [ + 'column in percentage mode with range if percentageMode is enabled ', + [ + { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + [], + { isPercentageMode: true, min: 0, max: 100 }, + ], + result, + mockConvertToColumnInPercentageMode, + ], ])('should return %s', (_, input, expected, mock) => { expect(convertMetricToColumns(...input)).toEqual(expected.map(expect.objectContaining)); if (mock) { diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts index d97f81c4c824..be4c92cd4ec7 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts @@ -8,7 +8,7 @@ import { METRIC_TYPES } from '@kbn/data-plugin/common'; import type { DataView } from '@kbn/data-views-plugin/common'; -import { SchemaConfig } from '../../..'; +import { PercentageModeConfig, SchemaConfig } from '../../..'; import { convertMetricAggregationColumnWithoutSpecialParams, convertToOtherParentPipelineAggColumns, @@ -19,20 +19,29 @@ import { convertToLastValueColumn, convertToCumulativeSumAggColumn, AggBasedColumn, + convertToColumnInPercentageMode, } from '../convert'; import { SUPPORTED_METRICS } from '../convert/supported_metrics'; import { getValidColumns } from '../utils'; export const convertMetricToColumns = ( - agg: SchemaConfig, + agg: SchemaConfig, dataView: DataView, - aggs: Array> + aggs: Array>, + percentageModeConfig: PercentageModeConfig = { isPercentageMode: false } ): AggBasedColumn[] | null => { const supportedAgg = SUPPORTED_METRICS[agg.aggType]; if (!supportedAgg) { return null; } + if (percentageModeConfig.isPercentageMode) { + const { isPercentageMode, ...minMax } = percentageModeConfig; + + const formulaColumn = convertToColumnInPercentageMode({ agg, dataView, aggs }, minMax); + return getValidColumns(formulaColumn); + } + switch (agg.aggType) { case METRIC_TYPES.AVG: case METRIC_TYPES.MIN: @@ -119,8 +128,6 @@ export const convertMetricToColumns = ( }); return getValidColumns(columns); } - case METRIC_TYPES.SERIAL_DIFF: - return null; default: return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/types/common.ts b/src/plugins/visualizations/common/convert_to_lens/types/common.ts index d792467e298d..c526a7116877 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/common.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/common.ts @@ -45,3 +45,12 @@ export interface NumberValueFormat { suffix?: string; }; } + +export interface MinMax { + min: number; + max: number; +} + +export type PercentageModeConfig = + | ({ isPercentageMode: true } & MinMax) + | { isPercentageMode: boolean }; diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 56108b1a1d63..ecfbbf34ad9c 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -8,7 +8,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; -import { AggBasedColumn, SchemaConfig } from '../../common'; +import { AggBasedColumn, PercentageModeConfig, SchemaConfig } from '../../common'; import { convertMetricToColumns } from '../../common/convert_to_lens/lib/metrics'; import { convertBucketToColumns } from '../../common/convert_to_lens/lib/buckets'; import { getCustomBucketsFromSiblingAggs } from '../../common/convert_to_lens/lib/utils'; @@ -46,8 +46,11 @@ export const getColumnsFromVis = ( } = {}, config?: { dropEmptyRowsInDateHistogram?: boolean; - } + } & (PercentageModeConfig | void) ) => { + const { dropEmptyRowsInDateHistogram, ...percentageModeConfig } = config ?? { + isPercentageMode: false, + }; const visSchemas = getVisSchemas(vis, { timefilter, timeRange: timefilter.getAbsoluteTime(), @@ -67,8 +70,8 @@ export const getColumnsFromVis = ( const metricsWithoutDuplicates = getMetricsWithoutDuplicates(visSchemas.metric); const aggs = metricsWithoutDuplicates as Array>; - const metricColumns = metricsWithoutDuplicates.flatMap((m) => - convertMetricToColumns(m, dataView, aggs) + const metricColumns = aggs.flatMap((m) => + convertMetricToColumns(m, dataView, aggs, percentageModeConfig) ); if (metricColumns.includes(null)) { @@ -81,7 +84,7 @@ export const getColumnsFromVis = ( const customBucketColumn = convertBucketToColumns( { agg: customBuckets[0], dataView, metricColumns: metrics, aggs }, false, - config?.dropEmptyRowsInDateHistogram + dropEmptyRowsInDateHistogram ); if (!customBucketColumn) { return null; @@ -95,7 +98,7 @@ export const getColumnsFromVis = ( dataView, false, metricColumns as AggBasedColumn[], - config?.dropEmptyRowsInDateHistogram + dropEmptyRowsInDateHistogram ); if (!bucketColumns) { return null; @@ -107,7 +110,7 @@ export const getColumnsFromVis = ( dataView, true, metricColumns as AggBasedColumn[], - config?.dropEmptyRowsInDateHistogram + dropEmptyRowsInDateHistogram ); if (!splitBucketColumns) { return null; diff --git a/test/functional/apps/console/_xjson.ts b/test/functional/apps/console/_xjson.ts index 386cda8ef32e..1535337a2a84 100644 --- a/test/functional/apps/console/_xjson.ts +++ b/test/functional/apps/console/_xjson.ts @@ -7,6 +7,7 @@ */ import expect from '@kbn/expect'; +import { rgbToHex } from '@elastic/eui'; import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getService, getPageObjects }: FtrProviderContext) => { @@ -14,10 +15,9 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const log = getService('log'); const PageObjects = getPageObjects(['common', 'console', 'header']); - describe("Console's XJSON features", function testXjson() { + describe('XJSON', function testXjson() { this.tags('includeFirefox'); before(async () => { - log.debug('navigateTo console'); await PageObjects.common.navigateToApp('console'); await retry.try(async () => { await PageObjects.console.collapseHelp(); @@ -28,22 +28,158 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { await PageObjects.console.clearTextArea(); }); - describe('with triple quoted strings', () => { + const executeRequest = async (request = '\n GET _search') => { + await PageObjects.console.enterRequest(request); + await PageObjects.console.clickPlay(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + describe('inline http request', () => { + it('should have method and path', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar'); + expect(await PageObjects.console.getRequestMethod()).to.be('PUT'); + expect(await PageObjects.console.getRequestPath()).to.be('foo/bar'); + }); + + it('should have optional query parameters', async () => { + await PageObjects.console.enterRequest('\n GET foo/bar?pretty'); + expect(await PageObjects.console.getRequestQueryParams()).to.be('pretty'); + }); + + it('should have optional request body', async () => { + await PageObjects.console.enterRequest('\n POST foo/bar\n {"foo": "bar"}'); + log.debug('request body: ' + (await PageObjects.console.getRequestBody())); + expect(await PageObjects.console.getRequestBody()).to.be('{"foo": "bar"}'); + }); + + it('should not have validation errors', async () => { + await PageObjects.console.enterRequest('\n GET foo/bar'); + expect(await PageObjects.console.hasErrorMarker()).to.be(false); + }); + + it('should have validation error for invalid method', async () => { + await PageObjects.console.enterRequest('\n FOO foo/bar'); + // Retry because the error marker is not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(true); + }); + }); + + it('should have validation error for invalid path', async () => { + await PageObjects.console.enterRequest('\n GET'); + // Retry because the error marker is not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(true); + }); + }); + + it('should have validation error for invalid body', async () => { + await PageObjects.console.enterRequest('\n POST foo/bar\n {"foo": "bar"'); + // Retry because the error marker is not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(true); + }); + }); + + it('should have correct syntax highlighting', async () => { + await PageObjects.console.enterRequest('\n GET foo/bar'); + expect(await PageObjects.console.getRequestLineHighlighting()).to.contain( + 'ace_method ace_whitespace ace_url ace_part ace_url ace_slash ace_url ace_part' + ); + }); + + it('should have correct syntax highlighting for method', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar'); + const color = await PageObjects.console.getRequestMethodColor(); + expect(rgbToHex(color)).to.be('#c80a68'); + }); + + it('should have correct syntax highlighting for path', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar'); + const color = await PageObjects.console.getRequestPathColor(); + expect(rgbToHex(color)).to.be('#00756c'); + }); + + it('should have correct syntax highlighting for query', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar?pretty'); + const color = await PageObjects.console.getRequestQueryColor(); + expect(rgbToHex(color)).to.be('#00756c'); + }); + + it('should have correct syntax highlighting for body', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar\n {"foo": "bar"}'); + const color = await PageObjects.console.getRequestBodyColor(); + expect(rgbToHex(color)).to.be('#343741'); + }); + + it('should have multiple bodies for _msearch requests', async () => { + await PageObjects.console.enterRequest( + '\nGET foo/_msearch \n{}\n{"query": {"match_all": {}}}\n{"index": "bar"}\n{"query": {"match_all": {}}}' + ); + // Retry because body elements are not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.getRequestBodyCount()).to.be(4); + }); + }); + + it('should not trigger error for multiple bodies for _msearch requests', async () => { + await PageObjects.console.enterRequest( + '\nGET foo/_msearch \n{}\n{"query": {"match_all": {}}}\n{"index": "bar"}\n{"query": {"match_all": {}}}' + ); + // Retry until typing is finished. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(false); + }); + }); + + it('should not trigger validation errors for multiple JSON blocks', async () => { + await PageObjects.console.enterRequest('\nPOST test/doc/1 \n{\n "foo": "bar"'); + await PageObjects.console.enterRequest('\nPOST test/doc/2 \n{\n "foo": "baz"'); + await PageObjects.console.enterRequest('\nPOST test/doc/3 \n{\n "foo": "qux"'); + // Retry until typing is finished. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(false); + }); + }); + it('should allow escaping quotation mark by wrapping it in triple quotes', async () => { await PageObjects.console.enterRequest( '\nPOST test/_doc/1 \n{\n "foo": """look "escaped" quotes"""' ); - await PageObjects.console.clickPlay(); - await PageObjects.header.waitUntilLoadingHasFinished(); + // Retry until typing is finished and validation errors are gone. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(false); + }); + }); + + it('should have correct syntax highlighting for inline comments', async () => { + await PageObjects.console.enterRequest( + '\nPOST test/_doc/1 \n{\n "foo": "bar" # inline comment' + ); + const color = await PageObjects.console.getCommentColor(); + expect(rgbToHex(color)).to.be('#41755c'); + }); + + it('should allow inline comments in request url row', async () => { + await executeRequest('\n GET _search // inline comment'); expect(await PageObjects.console.hasErrorMarker()).to.be(false); + expect(await PageObjects.console.getResponseStatus()).to.eql(200); + }); + + it('should allow inline comments in request body', async () => { + await executeRequest('\n GET _search \n{\n "query": {\n "match_all": {} // inline comment'); + expect(await PageObjects.console.hasErrorMarker()).to.be(false); + expect(await PageObjects.console.getResponseStatus()).to.eql(200); + }); + + it('should print warning for deprecated request', async () => { + await executeRequest('\nGET .kibana'); + expect(await PageObjects.console.responseHasDeprecationWarning()).to.be(true); }); - }); - describe('with invalid syntax', () => { - it('should trigger validation errors', async () => { - await PageObjects.console.enterRequest('\nGET test/doc/1 \n{\n "foo": \'\''); - expect(await PageObjects.console.hasInvalidSyntax()).to.be(true); - expect(await PageObjects.console.hasErrorMarker()).to.be(true); + it('should not print warning for non-deprecated request', async () => { + await executeRequest('\n GET _search'); + expect(await PageObjects.console.responseHasDeprecationWarning()).to.be(false); }); }); }); diff --git a/test/functional/apps/discover/group2/_sql_view.ts b/test/functional/apps/discover/group2/_sql_view.ts index 89a2e0608577..d8ec69db66ee 100644 --- a/test/functional/apps/discover/group2/_sql_view.ts +++ b/test/functional/apps/discover/group2/_sql_view.ts @@ -53,7 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await testSubjects.exists('toggleFieldFilterButton')).to.be(true); expect(await testSubjects.exists('fieldTypesHelpButton')).to.be(true); await testSubjects.click('field-@message-showDetails'); - expect(await testSubjects.exists('discoverFieldListPanelEditItem')).to.be(true); + expect(await testSubjects.exists('discoverFieldListPanelEdit-@message')).to.be(true); await PageObjects.discover.selectTextBaseLang('SQL'); diff --git a/test/functional/apps/management/_index_pattern_filter.ts b/test/functional/apps/management/_index_pattern_filter.ts index edfd32952da4..afa64c474d39 100644 --- a/test/functional/apps/management/_index_pattern_filter.ts +++ b/test/functional/apps/management/_index_pattern_filter.ts @@ -15,9 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['settings']); const esArchiver = getService('esArchiver'); - // FLAKY: https://github.com/elastic/kibana/issues/128558 - // Failing: See https://github.com/elastic/kibana/issues/130190 - describe.skip('data view filter', function describeIndexTests() { + describe('index pattern filter', function describeIndexTests() { before(async function () { await esArchiver.emptyKibanaIndex(); await kibanaServer.uiSettings.replace({}); diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 9f662a09146e..0069682f828d 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -227,6 +227,19 @@ export class ConsolePageObject extends FtrService { return await this.find.existsByCssSelector('.ace_error'); } + public async getTokenColor(token: string) { + const element = await this.find.byClassName(token); + return await element.getComputedStyle('color'); + } + + public async responseHasDeprecationWarning() { + // Retry for a while to allow the deprecation warning to appear + return await this.retry.try(async () => { + const response = await this.getResponse(); + return response.trim().startsWith('#!'); + }); + } + public async clickFoldWidget() { const widget = await this.find.byCssSelector('.ace_fold-widget'); await widget.click(); @@ -404,4 +417,92 @@ export class ConsolePageObject extends FtrService { const button = await this.testSubjects.find('consoleMenuAutoIndent'); await button.click(); } + + public async getRequestMethod() { + const requestEditor = await this.getRequestEditor(); + const requestMethod = await requestEditor.findByClassName('ace_method'); + const method = await requestMethod.getVisibleText(); + return method.trim(); + } + + public async getRequestPath() { + const requestEditor = await this.getRequestEditor(); + const requestPath = await requestEditor.findAllByCssSelector('.ace_url'); + const path = []; + for (const pathPart of requestPath) { + const className = await pathPart.getAttribute('class'); + if (className.includes('ace_param')) { + // This is a parameter, we don't want to include it in the path + break; + } + path.push(await pathPart.getVisibleText()); + } + return path.join('').trim(); + } + + public async getRequestQueryParams() { + const requestEditor = await this.getRequestEditor(); + const requestQueryParams = await requestEditor.findAllByCssSelector('.ace_url.ace_param'); + + if (requestQueryParams.length === 0) { + // No query params + return; + } + + const params = []; + for (const param of requestQueryParams) { + params.push(await param.getVisibleText()); + } + return params.join('').trim(); + } + + public async getRequestBody() { + let request = await this.getRequest(); + // Remove new lines at the beginning of the request + request = request.replace(/^\n/, ''); + const method = await this.getRequestMethod(); + const path = await this.getRequestPath(); + const query = await this.getRequestQueryParams(); + + if (query) { + return request.replace(`${method} ${path}?${query}`, '').trim(); + } + + return request.replace(`${method} ${path}`, '').trim(); + } + + public async getRequestLineHighlighting() { + const requestEditor = await this.getRequestEditor(); + const requestLine = await requestEditor.findAllByCssSelector('.ace_line > *'); + const line = []; + for (const linePart of requestLine) { + line.push(await linePart.getAttribute('class')); + } + return line.join(' '); + } + + public async getRequestMethodColor() { + return await this.getTokenColor('ace_method'); + } + + public async getRequestPathColor() { + return await this.getTokenColor('ace_url'); + } + + public async getRequestQueryColor() { + return await this.getTokenColor('ace_param'); + } + + public async getRequestBodyColor() { + return await this.getTokenColor('ace_paren'); + } + + public async getCommentColor() { + return await this.getTokenColor('ace_comment'); + } + + public async getRequestBodyCount() { + const body = await this.getRequestBody(); + return body.split('\n').length; + } } diff --git a/test/functional/services/dashboard/panel_actions.ts b/test/functional/services/dashboard/panel_actions.ts index 17c2ffee733b..79370e8e6af6 100644 --- a/test/functional/services/dashboard/panel_actions.ts +++ b/test/functional/services/dashboard/panel_actions.ts @@ -179,6 +179,14 @@ export class DashboardPanelActionsService extends FtrService { return searchSessionId; } + async getSearchResponseByTitle(title: string) { + await this.openInspectorByTitle(title); + await this.inspector.openInspectorRequestsView(); + const response = await this.inspector.getResponse(); + await this.inspector.close(); + return response; + } + async openInspector(parent?: WebElementWrapper) { await this.openContextMenu(parent); const exists = await this.testSubjects.exists(OPEN_INSPECTOR_TEST_SUBJ); diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts index 1917fa6dcdc5..0a7468a5b9be 100644 --- a/test/functional/services/inspector.ts +++ b/test/functional/services/inspector.ts @@ -17,6 +17,7 @@ export class InspectorService extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly find = this.ctx.getService('find'); private readonly comboBox = this.ctx.getService('comboBox'); + private readonly monacoEditor = this.ctx.getService('monacoEditor'); private async getIsEnabled(): Promise { const ariaDisabled = await this.testSubjects.getAttribute('openInspectorButton', 'disabled'); @@ -212,6 +213,7 @@ export class InspectorService extends FtrService { * Opens inspector requests view */ public async openInspectorRequestsView(): Promise { + if (!(await this.testSubjects.exists('inspectorViewChooser'))) return; await this.openInspectorView('Requests'); } @@ -253,6 +255,15 @@ export class InspectorService extends FtrService { return this.testSubjects.find('inspectorRequestDetailResponse'); } + public async getResponse(): Promise> { + await (await this.getOpenRequestDetailResponseButton()).click(); + + await this.monacoEditor.waitCodeEditorReady('inspectorRequestCodeViewerContainer'); + const responseString = await this.monacoEditor.getCodeEditorValue(); + this.log.debug('Response string from inspector:', responseString); + return JSON.parse(responseString); + } + /** * Returns true if the value equals the combobox options list * @param value default combobox single option text diff --git a/test/interpreter_functional/snapshots/baseline/combined_test3.json b/test/interpreter_functional/snapshots/baseline/combined_test3.json index 49daaed3c5b8..922d266f7e18 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test3.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test3.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/final_output_test.json b/test/interpreter_functional/snapshots/baseline/final_output_test.json index 49daaed3c5b8..922d266f7e18 100644 --- a/test/interpreter_functional/snapshots/baseline/final_output_test.json +++ b/test/interpreter_functional/snapshots/baseline/final_output_test.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_all_data.json b/test/interpreter_functional/snapshots/baseline/metric_all_data.json index 1c7a6cb857ed..3b8553435624 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_all_data.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"bytes","params":null},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"bytes","params":null},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_empty_data.json b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json index 1f93a487bee2..ef645bdb30af 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_empty_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json index 78135325fac6..90d572ab720f 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json index 4ff228305a1d..2bb96cbcb4c0 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json index 7a91e2cae2ba..2c9c785e4ab2 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_2.json b/test/interpreter_functional/snapshots/baseline/partial_test_2.json index 49daaed3c5b8..922d266f7e18 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_2.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_2.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test3.json b/test/interpreter_functional/snapshots/baseline/step_output_test3.json index 49daaed3c5b8..922d266f7e18 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test3.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test3.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test3.json b/test/interpreter_functional/snapshots/session/combined_test3.json index 49daaed3c5b8..922d266f7e18 100644 --- a/test/interpreter_functional/snapshots/session/combined_test3.json +++ b/test/interpreter_functional/snapshots/session/combined_test3.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/final_output_test.json b/test/interpreter_functional/snapshots/session/final_output_test.json index 49daaed3c5b8..922d266f7e18 100644 --- a/test/interpreter_functional/snapshots/session/final_output_test.json +++ b/test/interpreter_functional/snapshots/session/final_output_test.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_all_data.json b/test/interpreter_functional/snapshots/session/metric_all_data.json index 1c7a6cb857ed..3b8553435624 100644 --- a/test/interpreter_functional/snapshots/session/metric_all_data.json +++ b/test/interpreter_functional/snapshots/session/metric_all_data.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"bytes","params":null},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"bytes","params":null},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_empty_data.json b/test/interpreter_functional/snapshots/session/metric_empty_data.json index 1f93a487bee2..ef645bdb30af 100644 --- a/test/interpreter_functional/snapshots/session/metric_empty_data.json +++ b/test/interpreter_functional/snapshots/session/metric_empty_data.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json index 78135325fac6..90d572ab720f 100644 --- a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json index 4ff228305a1d..2bb96cbcb4c0 100644 --- a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json index 7a91e2cae2ba..2c9c785e4ab2 100644 --- a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_2.json b/test/interpreter_functional/snapshots/session/partial_test_2.json index 49daaed3c5b8..922d266f7e18 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_2.json +++ b/test/interpreter_functional/snapshots/session/partial_test_2.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test3.json b/test/interpreter_functional/snapshots/session/step_output_test3.json index 49daaed3c5b8..922d266f7e18 100644 --- a/test/interpreter_functional/snapshots/session/step_output_test3.json +++ b/test/interpreter_functional/snapshots/session/step_output_test3.json @@ -1 +1 @@ -{"as":"legacyMetricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/test_suites/run_pipeline/event_annotation/fetch_event_annotations.ts b/test/interpreter_functional/test_suites/run_pipeline/event_annotation/fetch_event_annotations.ts index 7616583de2e6..bf33c24f0239 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/event_annotation/fetch_event_annotations.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/event_annotation/fetch_event_annotations.ts @@ -18,8 +18,7 @@ export default function ({ }: FtrProviderContext & { updateBaselines: boolean }) { let expectExpression: ExpectExpression; - // Failing: See https://github.com/elastic/kibana/issues/140113 - describe.skip('fetch event annotation tests', () => { + describe('fetch event annotation tests', () => { before(() => { expectExpression = expectExpressionProvider({ getService, updateBaselines }); }); diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 4633a374ee9d..a3968330e142 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -91,20 +91,14 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'unifiedSearch.autocomplete.valueSuggestions.tiers (array)', 'unifiedSearch.autocomplete.valueSuggestions.timeout (duration)', 'data.search.aggs.shardDelay.enabled (boolean)', - 'data.search.sessions.cleanupInterval (duration)', 'data.search.sessions.defaultExpiration (duration)', 'data.search.sessions.enabled (boolean)', - 'data.search.sessions.expireInterval (duration)', 'data.search.sessions.management.expiresSoonWarning (duration)', 'data.search.sessions.management.maxSessions (number)', 'data.search.sessions.management.refreshInterval (duration)', 'data.search.sessions.management.refreshTimeout (duration)', 'data.search.sessions.maxUpdateRetries (number)', - 'data.search.sessions.monitoringTaskTimeout (duration)', - 'data.search.sessions.notTouchedInProgressTimeout (duration)', 'data.search.sessions.notTouchedTimeout (duration)', - 'data.search.sessions.pageSize (number)', - 'data.search.sessions.trackingInterval (duration)', 'enterpriseSearch.host (string)', 'guidedOnboarding.ui (boolean)', 'home.disableWelcomeScreen (boolean)', diff --git a/versions.json b/versions.json index a39a1412c46f..30514180e5c1 100644 --- a/versions.json +++ b/versions.json @@ -14,7 +14,7 @@ "previousMinor": true }, { - "version": "8.4.3", + "version": "8.4.4", "branch": "8.4", "currentMajor": true, "previousMinor": true diff --git a/x-pack/examples/files_example/common/index.ts b/x-pack/examples/files_example/common/index.ts index b9b30fac5cb5..1586d92c4c05 100644 --- a/x-pack/examples/files_example/common/index.ts +++ b/x-pack/examples/files_example/common/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FileKind } from '@kbn/files-plugin/common'; +import type { FileKind, FileImageMetadata } from '@kbn/files-plugin/common'; export const PLUGIN_ID = 'filesExample'; export const PLUGIN_NAME = 'filesExample'; @@ -27,3 +27,5 @@ export const exampleFileKind: FileKind = { update: httpTags, }, }; + +export type MyImageMetadata = FileImageMetadata; diff --git a/x-pack/examples/files_example/public/components/app.tsx b/x-pack/examples/files_example/public/components/app.tsx index 124bc842af5c..cf0f4461b8b6 100644 --- a/x-pack/examples/files_example/public/components/app.tsx +++ b/x-pack/examples/files_example/public/components/app.tsx @@ -21,8 +21,9 @@ import { } from '@elastic/eui'; import { CoreStart } from '@kbn/core/public'; -import { DetailsFlyout } from './details_flyout'; +import type { MyImageMetadata } from '../../common'; import type { FileClients } from '../types'; +import { DetailsFlyout } from './details_flyout'; import { ConfirmButtonIcon } from './confirm_button'; import { Modal } from './modal'; @@ -31,7 +32,7 @@ interface FilesExampleAppDeps { notifications: CoreStart['notifications']; } -type ListResponse = FilesClientResponses['list']; +type ListResponse = FilesClientResponses['list']; export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) => { const { data, isLoading, error, refetch } = useQuery(['files'], () => @@ -39,7 +40,7 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) = ); const [showUploadModal, setShowUploadModal] = useState(false); const [isDeletingFile, setIsDeletingFile] = useState(false); - const [selectedItem, setSelectedItem] = useState(); + const [selectedItem, setSelectedItem] = useState>(); const renderToolsRight = () => { return [ @@ -55,7 +56,7 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) = const items = [...(data?.files ?? [])].reverse(); - const columns: EuiInMemoryTableProps['columns'] = [ + const columns: EuiInMemoryTableProps>['columns'] = [ { field: 'name', name: 'Name', diff --git a/x-pack/examples/files_example/public/components/details_flyout.tsx b/x-pack/examples/files_example/public/components/details_flyout.tsx index 48eaa9a6ab8e..a417752d1a66 100644 --- a/x-pack/examples/files_example/public/components/details_flyout.tsx +++ b/x-pack/examples/files_example/public/components/details_flyout.tsx @@ -7,7 +7,6 @@ import moment from 'moment'; import type { FunctionComponent } from 'react'; import React from 'react'; -import { css } from '@emotion/react'; import { EuiFlyout, EuiFlyoutHeader, @@ -22,11 +21,13 @@ import { EuiSpacer, } from '@elastic/eui'; import type { FileJSON } from '@kbn/files-plugin/common'; +import { css } from '@emotion/react'; +import type { MyImageMetadata } from '../../common'; import { FileClients } from '../types'; import { Image } from '../imports'; interface Props { - file: FileJSON; + file: FileJSON; files: FileClients; onDismiss: () => void; } @@ -40,8 +41,24 @@ export const DetailsFlyout: FunctionComponent = ({ files, file, onDismiss +
+ {file.alt +
+ = ({ files, file, onDismiss }, ]} /> - - {file.alt
diff --git a/x-pack/examples/files_example/public/components/modal.tsx b/x-pack/examples/files_example/public/components/modal.tsx index a4b54b694713..9d323b240f41 100644 --- a/x-pack/examples/files_example/public/components/modal.tsx +++ b/x-pack/examples/files_example/public/components/modal.tsx @@ -8,11 +8,11 @@ import type { FunctionComponent } from 'react'; import React from 'react'; import { EuiModal, EuiModalHeader, EuiModalBody, EuiText } from '@elastic/eui'; -import { exampleFileKind } from '../../common'; +import { exampleFileKind, MyImageMetadata } from '../../common'; import { FilesClient, UploadFile } from '../imports'; interface Props { - client: FilesClient; + client: FilesClient; onDismiss: () => void; onUploaded: () => void; } diff --git a/x-pack/examples/files_example/public/plugin.ts b/x-pack/examples/files_example/public/plugin.ts index 5dd961278dbd..98a6b6f6e460 100644 --- a/x-pack/examples/files_example/public/plugin.ts +++ b/x-pack/examples/files_example/public/plugin.ts @@ -6,7 +6,7 @@ */ import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; -import { PLUGIN_ID, PLUGIN_NAME, exampleFileKind } from '../common'; +import { PLUGIN_ID, PLUGIN_NAME, exampleFileKind, MyImageMetadata } from '../common'; import { FilesExamplePluginsStart, FilesExamplePluginsSetup } from './types'; export class FilesExamplePlugin @@ -28,8 +28,8 @@ export class FilesExamplePlugin coreStart, { files: { - unscoped: deps.files.filesClientFactory.asUnscoped(), - example: deps.files.filesClientFactory.asScoped(exampleFileKind.id), + unscoped: deps.files.filesClientFactory.asUnscoped(), + example: deps.files.filesClientFactory.asScoped(exampleFileKind.id), }, }, params diff --git a/x-pack/examples/files_example/public/types.ts b/x-pack/examples/files_example/public/types.ts index b1e19b13d1b9..fbc058d9aec3 100644 --- a/x-pack/examples/files_example/public/types.ts +++ b/x-pack/examples/files_example/public/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { MyImageMetadata } from '../common'; import type { FilesSetup, FilesStart, ScopedFilesClient, FilesClient } from './imports'; export interface FilesExamplePluginsSetup { @@ -16,9 +17,9 @@ export interface FilesExamplePluginsStart { } export interface FileClients { - unscoped: FilesClient; + unscoped: FilesClient; // Example file kind - example: ScopedFilesClient; + example: ScopedFilesClient; } export interface AppPluginStartDependencies { diff --git a/x-pack/examples/third_party_maps_source_example/public/classes/custom_raster_source.tsx b/x-pack/examples/third_party_maps_source_example/public/classes/custom_raster_source.tsx index 8521e4333be7..d36ed2485b5b 100644 --- a/x-pack/examples/third_party_maps_source_example/public/classes/custom_raster_source.tsx +++ b/x-pack/examples/third_party_maps_source_example/public/classes/custom_raster_source.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import _ from 'lodash'; import { ReactElement } from 'react'; import { calculateBounds } from '@kbn/data-plugin/common'; import { FieldFormatter, MIN_ZOOM, MAX_ZOOM } from '@kbn/maps-plugin/common'; @@ -16,15 +17,18 @@ import type { Timeslice, } from '@kbn/maps-plugin/common/descriptor_types'; import type { + DataRequest, IField, ImmutableSourceProperty, - ITMSSource, + IRasterSource, SourceEditorArgs, } from '@kbn/maps-plugin/public'; +import { RasterTileSourceData } from '@kbn/maps-plugin/public/classes/sources/raster_source'; +import { RasterTileSource } from 'maplibre-gl'; type CustomRasterSourceDescriptor = AbstractSourceDescriptor; -export class CustomRasterSource implements ITMSSource { +export class CustomRasterSource implements IRasterSource { static type = 'CUSTOM_RASTER'; readonly _descriptor: CustomRasterSourceDescriptor; @@ -39,6 +43,25 @@ export class CustomRasterSource implements ITMSSource { this._descriptor = sourceDescriptor; } + async canSkipSourceUpdate( + dataRequest: DataRequest, + nextRequestMeta: DataRequestMeta + ): Promise { + const prevMeta = dataRequest.getMeta(); + if (!prevMeta) { + return Promise.resolve(false); + } + + return Promise.resolve(_.isEqual(prevMeta.timeslice, nextRequestMeta.timeslice)); + } + + isSourceStale(mbSource: RasterTileSource, sourceData: RasterTileSourceData): boolean { + if (!sourceData.url) { + return false; + } + return mbSource.tiles?.[0] !== sourceData.url; + } + cloneDescriptor(): CustomRasterSourceDescriptor { return { type: this._descriptor.type, diff --git a/x-pack/plugins/actions/server/sub_action_framework/README.md b/x-pack/plugins/actions/server/sub_action_framework/README.md index 7c2ab0755a0a..d3ca9c714346 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/README.md +++ b/x-pack/plugins/actions/server/sub_action_framework/README.md @@ -351,6 +351,7 @@ plugins.actions.registerSubActionConnectorType({ minimumLicenseRequired: 'platinum' as const, schema: { config: TestConfigSchema, secrets: TestSecretsSchema }, Service: TestSubActionConnector, + renderParameterTemplates: renderTestTemplate }); ``` diff --git a/x-pack/plugins/actions/server/sub_action_framework/register.test.ts b/x-pack/plugins/actions/server/sub_action_framework/register.test.ts index d4c9290b2de2..6788ca6e5e6f 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/register.test.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/register.test.ts @@ -18,6 +18,9 @@ import { import { register } from './register'; describe('Registration', () => { + const renderedVariables = { body: '' }; + const mockRenderParameterTemplates = jest.fn().mockReturnValue(renderedVariables); + const connector = { id: '.test', name: 'Test', @@ -28,6 +31,7 @@ describe('Registration', () => { secrets: TestSecretsSchema, }, Service: TestSubActionConnector, + renderParameterTemplates: mockRenderParameterTemplates, }; const actionTypeRegistry = actionTypeRegistryMock.create(); @@ -35,7 +39,6 @@ describe('Registration', () => { const logger = loggingSystemMock.createLogger(); beforeEach(() => { - jest.resetAllMocks(); jest.clearAllMocks(); }); @@ -54,7 +57,27 @@ describe('Registration', () => { minimumLicenseRequired: connector.minimumLicenseRequired, supportedFeatureIds: connector.supportedFeatureIds, validate: expect.anything(), - executor: expect.anything(), + executor: expect.any(Function), + renderParameterTemplates: expect.any(Function), + }); + }); + + it('registers the renderParameterTemplates correctly', async () => { + register({ + actionTypeRegistry, + connector, + configurationUtilities: mockedActionsConfig, + logger, }); + + const params = {}; + const variables = {}; + const actionId = 'action-id'; + + const { renderParameterTemplates } = actionTypeRegistry.register.mock.calls[0][0]; + const rendered = renderParameterTemplates?.(params, variables, actionId); + + expect(mockRenderParameterTemplates).toHaveBeenCalledWith(params, variables, actionId); + expect(rendered).toBe(renderedVariables); }); }); diff --git a/x-pack/plugins/actions/server/sub_action_framework/register.ts b/x-pack/plugins/actions/server/sub_action_framework/register.ts index 077339731a2a..9d5bd91a8886 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/register.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/register.ts @@ -54,5 +54,6 @@ export const register = { responseSchema, headers, ...config - }: { - url: string; - responseSchema: Type; - method?: Method; - } & AxiosRequestConfig): Promise> { + }: SubActionRequestParams): Promise> { try { this.assertURL(url); this.ensureUriAllowed(url); diff --git a/x-pack/plugins/actions/server/sub_action_framework/types.ts b/x-pack/plugins/actions/server/sub_action_framework/types.ts index f584d73d2444..26b1fd20020d 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/types.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/types.ts @@ -6,12 +6,18 @@ */ import type { Type } from '@kbn/config-schema'; -import { Logger } from '@kbn/logging'; +import type { Logger } from '@kbn/logging'; import type { LicenseType } from '@kbn/licensing-plugin/common/types'; -import { ActionsConfigurationUtilities } from '../actions_config'; -import { ActionTypeParams, Services, ValidatorType as ValidationSchema } from '../types'; -import { SubActionConnector } from './sub_action_connector'; +import type { Method, AxiosRequestConfig } from 'axios'; +import type { ActionsConfigurationUtilities } from '../actions_config'; +import type { + ActionTypeParams, + RenderParameterTemplates, + Services, + ValidatorType as ValidationSchema, +} from '../types'; +import type { SubActionConnector } from './sub_action_connector'; export interface ServiceParams { /** @@ -26,6 +32,12 @@ export interface ServiceParams { services: Services; } +export type SubActionRequestParams = { + url: string; + responseSchema: Type; + method?: Method; +} & AxiosRequestConfig; + export type IService = new ( params: ServiceParams ) => SubActionConnector; @@ -68,6 +80,7 @@ export interface SubActionConnectorType { }; validators?: Array | SecretsValidator>; Service: IService; + renderParameterTemplates?: RenderParameterTemplates; } export interface ExecutorParams extends ActionTypeParams { diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index c92761ad0a28..3806dae00c23 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -108,6 +108,12 @@ export interface ActionValidationService { isUriAllowed(uri: string): boolean; } +export type RenderParameterTemplates = ( + params: Params, + variables: Record, + actionId?: string +) => Params; + export interface ActionType< Config extends ActionTypeConfig = ActionTypeConfig, Secrets extends ActionTypeSecrets = ActionTypeSecrets, @@ -126,11 +132,7 @@ export interface ActionType< connector?: (config: Config, secrets: Secrets) => string | null; }; - renderParameterTemplates?( - params: Params, - variables: Record, - actionId?: string - ): Params; + renderParameterTemplates?: RenderParameterTemplates; executor: ExecutorType; } 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 6c8da64cb0e8..51f9e3870491 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 @@ -235,7 +235,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ 'xpack.aiops.explainLogRateSpikes.spikeAnalysisTableGroups.groupColumnTooltip', { defaultMessage: - 'Displays field values unique to the group. Expand row to see all values.', + 'Displays field/value pairs unique to the group. Expand row to see all field/value pairs.', } )} > @@ -280,7 +280,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ +{Object.keys(repeatedValues).length}{' '}
diff --git a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap index 1e340f6ffc75..508d8aac06da 100644 --- a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap @@ -1075,6 +1075,9 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the "properties": { "kuery_fields": { "type": "keyword" + }, + "total": { + "type": "long" } } }, diff --git a/x-pack/plugins/apm/ftr_e2e/cypress.config.ts b/x-pack/plugins/apm/ftr_e2e/cypress.config.ts index 7a92b84ac36b..bcccae43adc7 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress.config.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress.config.ts @@ -6,9 +6,10 @@ */ import { defineConfig } from 'cypress'; -import { plugin } from './cypress/plugins'; +import { setupNodeEvents } from './setup_cypress_node_events'; module.exports = defineConfig({ + projectId: 'omwh6f', fileServerFolder: './cypress', fixturesFolder: './cypress/fixtures', screenshotsFolder: './cypress/screenshots', @@ -18,16 +19,16 @@ module.exports = defineConfig({ defaultCommandTimeout: 30000, execTimeout: 120000, pageLoadTimeout: 120000, - viewportHeight: 900, + viewportHeight: 1800, viewportWidth: 1440, - video: false, - screenshotOnRunFailure: false, + video: true, + videoUploadOnPasses: false, + screenshotOnRunFailure: true, + retries: { + runMode: 1, + }, e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. - setupNodeEvents(on, config) { - plugin(on, config); - }, + setupNodeEvents, baseUrl: 'http://localhost:5601', supportFile: './cypress/support/e2e.ts', specPattern: './cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts index 20577f8bf579..2efebecf2575 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts @@ -89,14 +89,27 @@ describe('Storage Explorer', () => { }); it('has a list of services and environments', () => { - cy.contains('opbeans-node'); - cy.contains('opbeans-java'); - cy.contains('opbeans-rum'); + cy.contains( + '[data-test-subj="apmStorageExplorerServiceLink"]', + 'opbeans-node' + ); + cy.contains( + '[data-test-subj="apmStorageExplorerServiceLink"]', + 'opbeans-java' + ); + cy.contains( + '[data-test-subj="apmStorageExplorerServiceLink"]', + 'opbeans-rum' + ); cy.get('td:contains(production)').should('have.length', 3); }); it('when clicking on a service it loads the service overview for that service', () => { - cy.contains('opbeans-node').click({ force: true }); + cy.contains( + '[data-test-subj="apmStorageExplorerServiceLink"]', + 'opbeans-node' + ).click(); + cy.url().should('include', '/apm/services/opbeans-node/overview'); cy.contains('h1', 'opbeans-node'); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lamba.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lambda.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lamba.cy.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lambda.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts b/x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts similarity index 62% rename from x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts rename to x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts index 8adaad0b71c6..0e3cd4796696 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts +++ b/x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts @@ -11,28 +11,13 @@ import { LogLevel, } from '@kbn/apm-synthtrace'; import { createEsClientForTesting } from '@kbn/test'; +import { some } from 'lodash'; +import del from 'del'; -// *********************************************************** -// This example plugins/index.ts can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ - -export const plugin: Cypress.PluginConfig = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - +export function setupNodeEvents( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +) { const client = createEsClientForTesting({ esUrl: config.env.ES_NODE, requestTimeout: config.env.ES_REQUEST_TIMEOUT, @@ -65,4 +50,24 @@ export const plugin: Cypress.PluginConfig = (on, config) => { return null; }, }); -}; + + on('after:spec', (spec, results) => { + // Delete videos that have no failures or retries + if (results && results.video) { + const failures = some(results.tests, (test) => { + return some(test.attempts, { state: 'failed' }); + }); + if (!failures) { + del(results.video); + } + } + }); + + on('before:browser:launch', (browser, launchOptions) => { + if (browser.name === 'electron' && browser.isHeadless) { + launchOptions.preferences.width = 1440; + launchOptions.preferences.height = 1600; + } + return launchOptions; + }); +} diff --git a/x-pack/plugins/apm/public/components/app/alerts_overview/alerts_overview_table.test.tsx b/x-pack/plugins/apm/public/components/app/alerts_overview/alerts_overview_table.test.tsx new file mode 100644 index 000000000000..711b7ff389fe --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/alerts_overview/alerts_overview_table.test.tsx @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, waitFor, act } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import React, { ReactNode } from 'react'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import * as useApmParamsHooks from '../../../hooks/use_apm_params'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import { CoreStart } from '@kbn/core/public'; +import { AlertsOverview } from '.'; + +const getAlertsStateTableMock = jest.fn(); + +function Wrapper({ children }: { children?: ReactNode }) { + const KibanaReactContext = createKibanaReactContext({ + triggersActionsUi: { + getAlertsStateTable: getAlertsStateTableMock.mockReturnValue( +
+ ), + alertsTableConfigurationRegistry: '', + }, + } as Partial); + + return ( + + + {children} + + + ); +} + +const renderOptions = { wrapper: Wrapper }; + +describe('AlertsTable', () => { + beforeEach(() => { + jest.spyOn(useApmParamsHooks as any, 'useApmParams').mockReturnValue({ + path: { + serviceName: 'opbeans', + }, + query: { + rangeFrom: 'now-24h', + rangeTo: 'now', + environment: 'testing', + }, + }); + jest.clearAllMocks(); + }); + + it('renders alerts table', async () => { + const { getByTestId } = render(, renderOptions); + + await waitFor(async () => { + expect(getByTestId('alerts-table')).toBeTruthy(); + }); + }); + it('should call alerts table with correct propts', async () => { + act(() => { + render(, renderOptions); + }); + + await waitFor(async () => { + expect(getAlertsStateTableMock).toHaveBeenCalledWith( + { + alertsTableConfigurationRegistry: '', + id: 'service-overview-alerts', + configurationId: 'observability', + featureIds: ['apm'], + query: { + bool: { + filter: [ + { + term: { 'service.name': 'opbeans' }, + }, + { + term: { 'service.environment': 'testing' }, + }, + ], + }, + }, + showExpandToDetails: false, + }, + {} + ); + }); + }); + + it('should call alerts table with active filter', async () => { + const { getByTestId } = render(, renderOptions); + + await act(async () => { + const inputEl = getByTestId('active'); + inputEl.click(); + }); + + await waitFor(async () => { + expect(getAlertsStateTableMock).toHaveBeenLastCalledWith( + { + alertsTableConfigurationRegistry: '', + id: 'service-overview-alerts', + configurationId: 'observability', + featureIds: ['apm'], + query: { + bool: { + filter: [ + { + term: { 'service.name': 'opbeans' }, + }, + { + term: { 'kibana.alert.status': 'active' }, + }, + { + term: { 'service.environment': 'testing' }, + }, + ], + }, + }, + showExpandToDetails: false, + }, + {} + ); + }); + }); + + it('should call alerts table with recovered filter', async () => { + const { getByTestId } = render(, renderOptions); + + await act(async () => { + const inputEl = getByTestId('recovered'); + inputEl.click(); + }); + + await waitFor(async () => { + expect(getAlertsStateTableMock).toHaveBeenLastCalledWith( + { + alertsTableConfigurationRegistry: '', + id: 'service-overview-alerts', + configurationId: 'observability', + featureIds: ['apm'], + query: { + bool: { + filter: [ + { + term: { 'service.name': 'opbeans' }, + }, + { + term: { 'kibana.alert.status': 'recovered' }, + }, + { + term: { 'service.environment': 'testing' }, + }, + ], + }, + }, + showExpandToDetails: false, + }, + {} + ); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx b/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx index 84be97ab0ac5..ecef396bb0c5 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { keyBy } from 'lodash'; import React from 'react'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useSearchServiceDestinationMetrics } from '../../../../context/time_range_metadata/use_search_service_destination_metrics'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { useBreakpoints } from '../../../../hooks/use_breakpoints'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; @@ -66,6 +67,9 @@ export function DependencyDetailOperationsList() { urlComparisonEnabled, }); + const { searchServiceDestinationMetrics } = + useSearchServiceDestinationMetrics({ rangeFrom, rangeTo, kuery }); + const primaryStatsFetch = useFetcher( (callApmApi) => { return callApmApi('GET /internal/apm/dependencies/operations', { @@ -76,11 +80,19 @@ export function DependencyDetailOperationsList() { end, environment, kuery, + searchServiceDestinationMetrics, }, }, }); }, - [dependencyName, start, end, environment, kuery] + [ + dependencyName, + start, + end, + environment, + kuery, + searchServiceDestinationMetrics, + ] ); const comparisonStatsFetch = useFetcher( @@ -99,11 +111,21 @@ export function DependencyDetailOperationsList() { offset, environment, kuery, + searchServiceDestinationMetrics, }, }, }); }, - [dependencyName, start, end, offset, environment, kuery, comparisonEnabled] + [ + dependencyName, + start, + end, + offset, + environment, + kuery, + comparisonEnabled, + searchServiceDestinationMetrics, + ] ); const columns: Array> = [ diff --git a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx index 1ea26c08e7fd..4e48dadb2b92 100644 --- a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx @@ -5,13 +5,10 @@ * 2.0. */ -import { isEmpty } from 'lodash'; -import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; -import React, { useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; +import React from 'react'; import moment from 'moment'; import { LogStream } from '@kbn/infra-plugin/public'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useFetcher } from '../../../hooks/use_fetcher'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { APIReturnType } from '../../../services/rest/create_call_apm_api'; @@ -32,7 +29,7 @@ export function ServiceLogs() { const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { data, status } = useFetcher( + const { data } = useFetcher( (callApmApi) => { if (start && end) { return callApmApi( @@ -54,32 +51,6 @@ export function ServiceLogs() { [environment, kuery, serviceName, start, end] ); - const noInfrastructureData = useMemo(() => { - return isEmpty(data?.containerIds) && isEmpty(data?.hostNames); - }, [data]); - - if (status === FETCH_STATUS.LOADING) { - return ( -
- -
- ); - } - - if (status === FETCH_STATUS.SUCCESS && noInfrastructureData) { - return ( - - {i18n.translate('xpack.apm.serviceLogs.noInfrastructureMessage', { - defaultMessage: 'There are no log messages to display.', - })} - - } - /> - ); - } - return ( - ) : undefined, + ), hidden: isMetricsTabHidden({ agentName, runtimeName, @@ -318,6 +318,9 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { label: i18n.translate('xpack.apm.home.serviceLogsTabLabel', { defaultMessage: 'Logs', }), + append: isServerlessAgent(runtimeName) && ( + + ), hidden: !agentName || isRumAgentName(agentName) || isMobileAgentName(agentName), }, diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts index b69aa1e6e019..253c8ed9e44d 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { ApmIndicesConfig } from '../../../routes/settings/apm_indices/get_apm_indices'; import { tasks } from './tasks'; import { @@ -444,4 +445,102 @@ describe('data telemetry collection tasks', () => { }); }); }); + + describe('service groups', () => { + const task = tasks.find((t) => t.name === 'service_groups'); + const savedObjectsClient = savedObjectsClientMock.create(); + + it('returns service group stats', async () => { + savedObjectsClient.find.mockResolvedValueOnce({ + page: 1, + per_page: 500, + total: 2, + saved_objects: [ + { + type: 'apm-service-group', + id: '0b6157f0-44bd-11ed-bdb7-bffab551cd4d', + namespaces: ['default'], + attributes: { + color: '#5094C4', + kuery: 'service.environment: production', + groupName: 'production', + }, + references: [], + score: 1, + }, + { + type: 'apm-service-group', + id: '0b6157f0-44bd-11ed-bdb7-bffab551cd4d', + namespaces: ['space-1'], + attributes: { + color: '#5094C4', + kuery: 'agent.name: go', + groupName: 'agent', + }, + references: [], + score: 0, + }, + ], + }); + + expect(await task?.executor({ savedObjectsClient } as any)).toEqual({ + service_groups: { + kuery_fields: ['service.environment', 'agent.name'], + total: 2, + }, + }); + }); + + it('should return stats from all spaces', () => { + expect(savedObjectsClient.find).toHaveBeenCalledWith({ + type: 'apm-service-group', + page: 1, + perPage: 500, + sortField: 'updated_at', + sortOrder: 'desc', + namespaces: ['*'], + }); + }); + + it('returns unique fields', async () => { + savedObjectsClient.find.mockResolvedValueOnce({ + page: 1, + per_page: 500, + total: 2, + saved_objects: [ + { + type: 'apm-service-group', + id: '0b6157f0-44bd-11ed-bdb7-bffab551cd4d', + namespaces: ['default'], + attributes: { + color: '#5094C4', + kuery: 'service.environment: production', + groupName: 'production', + }, + references: [], + score: 1, + }, + { + type: 'apm-service-group', + id: '0b6157f0-44bd-11ed-bdb7-bffab551cd4d', + namespaces: ['default'], + attributes: { + color: '#5094C4', + kuery: 'service.environment: production and agent.name: go', + groupName: 'agent', + }, + references: [], + score: 0, + }, + ], + }); + + expect(await task?.executor({ savedObjectsClient } as any)).toEqual({ + service_groups: { + kuery_fields: ['service.environment', 'agent.name'], + total: 2, + }, + }); + }); + }); }); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index 430930fb3f91..2235de6c0be9 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -5,7 +5,7 @@ * 2.0. */ import { fromKueryExpression } from '@kbn/es-query'; -import { flatten, merge, sortBy, sum, pickBy } from 'lodash'; +import { flatten, merge, sortBy, sum, pickBy, uniq } from 'lodash'; import { createHash } from 'crypto'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; @@ -15,6 +15,7 @@ import { AGENT_NAMES, RUM_AGENT_NAMES } from '../../../../common/agent_name'; import { SavedServiceGroup, APM_SERVICE_GROUP_SAVED_OBJECT_TYPE, + MAX_NUMBER_OF_SERVICE_GROUPS, } from '../../../../common/service_groups'; import { getKueryFields } from '../../helpers/get_kuery_fields'; import { @@ -1134,9 +1135,10 @@ export const tasks: TelemetryTask[] = [ const response = await savedObjectsClient.find({ type: APM_SERVICE_GROUP_SAVED_OBJECT_TYPE, page: 1, - perPage: 50, + perPage: MAX_NUMBER_OF_SERVICE_GROUPS, sortField: 'updated_at', sortOrder: 'desc', + namespaces: ['*'], }); const kueryNodes = response.saved_objects.map( @@ -1147,7 +1149,8 @@ export const tasks: TelemetryTask[] = [ return { service_groups: { - kuery_fields: kueryFields, + kuery_fields: uniq(kueryFields), + total: response.total ?? 0, }, }; }, diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts index f33c9e03f6ca..4430389785e1 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts @@ -235,6 +235,7 @@ export const apmSchema: MakeSchemaFrom = { }, service_groups: { kuery_fields: { type: 'array', items: { type: 'keyword' } }, + total: long, }, per_service: { type: 'array', items: { ...apmPerServiceSchema } }, tasks: { diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts index 16e1b926d578..ceadcbfc1ded 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts @@ -173,6 +173,7 @@ export interface APMUsage { }; service_groups: { kuery_fields: string[]; + total: number; }; per_service: APMPerService[]; tasks: Record< diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client.ts index 4cd9df6bf213..5cc9f8bb427c 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client.ts @@ -9,7 +9,10 @@ import { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; import { APMRouteHandlerResources } from '../../../../routes/typings'; import { getInfraMetricIndices } from '../../get_infra_metric_indices'; -type InfraMetricsSearchParams = Omit; +type InfraMetricsSearchParams = Omit & { + size: number; + track_total_hits: boolean | number; +}; export type InfraMetricsClient = ReturnType; diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts index 1f2e270b7d69..3c688f9aaa48 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts @@ -10,23 +10,26 @@ import { rangeQuery, termQuery, } from '@kbn/observability-plugin/server'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { isFiniteNumber } from '@kbn/observability-plugin/common/utils/is_finite_number'; import { EVENT_OUTCOME, SPAN_DESTINATION_SERVICE_RESOURCE, - SPAN_DURATION, + SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, + SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM, SPAN_NAME, } from '../../../common/elasticsearch_fieldnames'; import { Environment } from '../../../common/environment_rt'; import { EventOutcome } from '../../../common/event_outcome'; import { environmentQuery } from '../../../common/utils/environment_query'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; -import { - calculateThroughputWithInterval, - calculateThroughputWithRange, -} from '../../lib/helpers/calculate_throughput'; -import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; +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 { + getDocumentTypeFilterForServiceDestinationStatistics, + getLatencyFieldForServiceDestinationStatistics, + getProcessorEventForServiceDestinationStatistics, +} from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; import { calculateImpactBuilder } from '../traces/calculate_impact_builder'; const MAX_NUM_OPERATIONS = 500; @@ -51,6 +54,7 @@ export async function getTopDependencyOperations({ offset, environment, kuery, + searchServiceDestinationMetrics, }: { setup: Setup; dependencyName: string; @@ -59,6 +63,7 @@ export async function getTopDependencyOperations({ offset?: string; environment: Environment; kuery: string; + searchServiceDestinationMetrics: boolean; }) { const { apmEventClient } = setup; @@ -68,10 +73,25 @@ export async function getTopDependencyOperations({ offset, }); + const { intervalString } = getBucketSizeForAggregatedTransactions({ + start: startWithOffset, + end: endWithOffset, + searchAggregatedServiceMetrics: searchServiceDestinationMetrics, + }); + + const field = getLatencyFieldForServiceDestinationStatistics( + searchServiceDestinationMetrics + ); + const aggs = { - duration: { - avg: { - field: SPAN_DURATION, + latency: { + ...(searchServiceDestinationMetrics + ? { sum: { field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM } } + : { avg: { field } }), + }, + count: { + sum: { + field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, }, }, successful: { @@ -94,7 +114,11 @@ export async function getTopDependencyOperations({ 'get_top_dependency_operations', { apm: { - events: [ProcessorEvent.span], + events: [ + getProcessorEventForServiceDestinationStatistics( + searchServiceDestinationMetrics + ), + ], }, body: { track_total_hits: false, @@ -106,6 +130,9 @@ export async function getTopDependencyOperations({ ...environmentQuery(environment), ...kqlQuery(kuery), ...termQuery(SPAN_DESTINATION_SERVICE_RESOURCE, dependencyName), + ...getDocumentTypeFilterForServiceDestinationStatistics( + searchServiceDestinationMetrics + ), ], }, }, @@ -117,18 +144,20 @@ export async function getTopDependencyOperations({ }, aggs: { over_time: { - date_histogram: getMetricsDateHistogramParams({ - start: startWithOffset, - end: endWithOffset, - metricsInterval: 60, - }), + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: startWithOffset, + max: endWithOffset, + }, + }, aggs, }, ...aggs, total_time: { - sum: { - field: SPAN_DURATION, - }, + sum: { field }, }, }, }, @@ -154,14 +183,28 @@ export async function getTopDependencyOperations({ bucket.over_time.buckets.forEach((dateBucket) => { const x = dateBucket.key + offsetInMs; + const latencyValue = isFiniteNumber(dateBucket.latency.value) + ? dateBucket.latency.value + : 0; + const count = isFiniteNumber(dateBucket.count.value) + ? dateBucket.count.value + : 1; timeseries.throughput.push({ x, - y: calculateThroughputWithInterval({ - value: dateBucket.doc_count, - bucketSize: 60, + y: calculateThroughputWithRange({ + start: startWithOffset, + end: endWithOffset, + value: searchServiceDestinationMetrics + ? dateBucket.count.value || 0 + : dateBucket.doc_count, }), }); - timeseries.latency.push({ x, y: dateBucket.duration.value }); + timeseries.latency.push({ + x, + y: searchServiceDestinationMetrics + ? latencyValue / count + : dateBucket.latency.value, + }); timeseries.failureRate.push({ x, y: @@ -174,13 +217,24 @@ export async function getTopDependencyOperations({ }); }); + const latencyValue = isFiniteNumber(bucket.latency.value) + ? bucket.latency.value + : 0; + const count = isFiniteNumber(bucket.count.value) + ? bucket.count.value + : 1; + return { spanName: bucket.key as string, - latency: bucket.duration.value, + latency: searchServiceDestinationMetrics + ? latencyValue / count + : bucket.latency.value, throughput: calculateThroughputWithRange({ start: startWithOffset, end: endWithOffset, - value: bucket.doc_count, + value: searchServiceDestinationMetrics + ? bucket.count.value || 0 + : bucket.doc_count, }), failureRate: bucket.failure.doc_count > 0 || bucket.successful.doc_count > 0 diff --git a/x-pack/plugins/apm/server/routes/dependencies/route.ts b/x-pack/plugins/apm/server/routes/dependencies/route.ts index 22a8c44e0de5..1ba2e92eee57 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/route.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/route.ts @@ -492,7 +492,10 @@ const dependencyOperationsRoute = createApmServerRoute({ environmentRt, kueryRt, offsetRt, - t.type({ dependencyName: t.string }), + t.type({ + dependencyName: t.string, + searchServiceDestinationMetrics: toBooleanRt, + }), ]), }), handler: async ( @@ -501,7 +504,15 @@ const dependencyOperationsRoute = createApmServerRoute({ const setup = await setupRequest(resources); const { - query: { dependencyName, start, end, environment, kuery, offset }, + query: { + dependencyName, + start, + end, + environment, + kuery, + offset, + searchServiceDestinationMetrics, + }, } = resources.params; const operations = await getTopDependencyOperations({ @@ -512,6 +523,7 @@ const dependencyOperationsRoute = createApmServerRoute({ offset, environment, kuery, + searchServiceDestinationMetrics, }); return { operations }; diff --git a/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts b/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts index 8f47c2d1664b..257233fada9c 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts @@ -29,6 +29,7 @@ export async function getContainerHostNames({ const response = await infraMetricsClient.search({ size: 0, + track_total_hits: false, query: { bool: { filter: [ 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 e66d855d1be9..5a78f5d97d5e 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 @@ -11,6 +11,7 @@ import { FAAS_COLDSTART_DURATION } from '../../../../../common/elasticsearch_fie import { Setup } from '../../../../lib/helpers/setup_request'; import { fetchAndTransformMetrics } from '../../fetch_and_transform_metrics'; import { ChartBase } from '../../types'; +import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; const chartBase: ChartBase = { title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStartDuration', { @@ -37,7 +38,7 @@ const chartBase: ChartBase = { ), }; -export function getColdStartDuration({ +export async function getColdStartDuration({ environment, kuery, setup, @@ -52,7 +53,7 @@ export function getColdStartDuration({ start: number; end: number; }) { - return fetchAndTransformMetrics({ + const coldStartDurationMetric = await fetchAndTransformMetrics({ environment, kuery, setup, @@ -64,4 +65,24 @@ export function getColdStartDuration({ additionalFilters: [{ exists: { field: FAAS_COLDSTART_DURATION } }], operationName: 'get_cold_start_duration', }); + + const [series] = coldStartDurationMetric.series; + + const data = series.data.map(({ x, y }) => ({ + x, + // Cold start duration duration is stored in ms, convert it to microseconds so it uses the same unit as the other charts + y: isFiniteNumber(y) ? y * 1000 : y, + })); + + return { + ...coldStartDurationMetric, + series: [ + { + ...series, + // Cold start duration duration is stored in ms, convert it to microseconds + overallValue: series.overallValue * 1000, + data, + }, + ], + }; } 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 0db9385615d2..0a27c66ef036 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 @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { euiLightVars as theme } from '@kbn/ui-theme'; import { FAAS_BILLED_DURATION } from '../../../../../common/elasticsearch_fieldnames'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; +import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; import { getVizColorForIndex } from '../../../../../common/viz_colors'; import { Setup } from '../../../../lib/helpers/setup_request'; import { getLatencyTimeseries } from '../../../transactions/get_latency_charts'; @@ -123,8 +124,23 @@ export async function getServerlessFunctionLatency({ getServerlessLantecySeries({ ...options, searchAggregatedTransactions }), ]); + const [series] = billedDurationMetrics.series; + const data = series.data.map(({ x, y }) => ({ + x, + // Billed duration is stored in ms, convert it to microseconds so it uses the same unit as the other chart + y: isFiniteNumber(y) ? y * 1000 : y, + })); + return { ...billedDurationMetrics, - series: [...billedDurationMetrics.series, ...serverlessDurationSeries], + series: [ + { + ...series, + // Billed duration is stored in ms, convert it to microseconds + overallValue: series.overallValue * 1000, + data, + }, + ...serverlessDurationSeries, + ], }; } diff --git a/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts b/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts index 2aa5312d66b9..b5ae2bbe093a 100644 --- a/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts +++ b/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts @@ -41,14 +41,16 @@ export async function getMetricsChartDataByAgent({ start, end, }; - if (isJavaAgentName(agentName)) { + const serverlessAgent = isServerlessAgent(serviceRuntimeName); + + if (isJavaAgentName(agentName) && !serverlessAgent) { return getJavaMetricsCharts({ ...options, serviceNodeName, }); } - if (isServerlessAgent(serviceRuntimeName)) { + if (serverlessAgent) { return getServerlessAgentMetricCharts(options); } diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts index ebce4f933871..b5b98890ac63 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts @@ -51,6 +51,7 @@ export const getServiceInstanceContainerMetadata = async ({ const response = await infraMetricsClient.search({ size: 1, + track_total_hits: false, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts index c1dcfe97e208..4b8d979a7dd8 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts @@ -43,6 +43,7 @@ export const getServiceOverviewContainerMetadata = async ({ const response = await infraMetricsClient.search({ size: 0, + track_total_hits: false, query: { bool: { filter: [ diff --git a/x-pack/plugins/cases/docs/openapi/README.md b/x-pack/plugins/cases/docs/openapi/README.md index 1ff3e24c2e91..cf0ea6c76da7 100644 --- a/x-pack/plugins/cases/docs/openapi/README.md +++ b/x-pack/plugins/cases/docs/openapi/README.md @@ -22,8 +22,13 @@ command in the `x-pack/plugins/cases/docs/openapi/` folder: Then you can generate the `bundled` files by running the following commands: - ``` - npx @redocly/openapi-cli bundle --ext yaml --output bundled.yaml entrypoint.yaml - npx @redocly/openapi-cli bundle --ext json --output bundled.json entrypoint.yaml - ``` + ``` + npx @redocly/cli bundle entrypoint.yaml --output bundled.yaml --ext yaml + npx @redocly/cli bundle entrypoint.yaml --output bundled.json --ext json + ``` + +You can run additional linting with the following command: + ``` + npx @redocly/cli lint bundled.json + ``` diff --git a/x-pack/plugins/cases/docs/openapi/bundled-min.json b/x-pack/plugins/cases/docs/openapi/bundled-min.json new file mode 100644 index 000000000000..b039f5065d45 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/bundled-min.json @@ -0,0 +1,1277 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Cases", + "description": "OpenAPI schema for Cases endpoints", + "version": "0.2", + "contact": { + "name": "Cases Team" + }, + "license": { + "name": "Elastic License 2.0", + "url": "https://www.elastic.co/licensing/elastic-license" + } + }, + "tags": [ + { + "name": "cases", + "description": "Case APIs enable you to open and track issues." + } + ], + "servers": [ + { + "url": "http://localhost:5601", + "description": "local" + } + ], + "paths": { + "/s/{spaceId}/api/cases/{caseId}/comments": { + "post": { + "summary": "Adds a comment or alert to a case.", + "operationId": "addCaseComment", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", + "tags": [ + "cases" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/case_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/add_case_comment_request" + }, + "examples": { + "createCaseCommentRequest": { + "$ref": "#/components/examples/add_comment_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/case_response_properties" + }, + "examples": { + "createCaseCommentResponse": { + "$ref": "#/components/examples/add_comment_response" + } + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "delete": { + "summary": "Deletes all comments and alerts from a case.", + "operationId": "deleteCaseComments", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", + "tags": [ + "cases" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/case_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "responses": { + "204": { + "description": "Indicates a successful call." + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "patch": { + "summary": "Updates a comment or alert in a case.", + "operationId": "updateCaseComment", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.\n", + "tags": [ + "cases" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/case_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/update_case_comment_request" + }, + "examples": { + "updateCaseCommentRequest": { + "$ref": "#/components/examples/update_comment_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/case_response_properties" + }, + "examples": { + "updateCaseCommentResponse": { + "$ref": "#/components/examples/update_comment_response" + } + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "get": { + "summary": "Retrieves all the comments from a case.", + "operationId": "getAllCaseComments", + "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", + "deprecated": true, + "tags": [ + "cases" + ], + "parameters": [ + { + "$ref": "#/components/parameters/case_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/case_response_properties" + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + } + }, + "components": { + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic" + }, + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "ApiKey" + } + }, + "parameters": { + "case_id": { + "in": "path", + "name": "caseId", + "description": "The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded.", + "required": true, + "schema": { + "type": "string", + "example": "9c235210-6834-11ea-a78c-6ffb38a34414" + } + }, + "space_id": { + "in": "path", + "name": "spaceId", + "description": "An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.", + "required": true, + "schema": { + "type": "string", + "example": "default" + } + }, + "kbn_xsrf": { + "schema": { + "type": "string" + }, + "in": "header", + "name": "kbn-xsrf", + "required": true + } + }, + "schemas": { + "case_response_closed_by_properties": { + "title": "Case response properties for closed_by", + "type": "object", + "nullable": true, + "properties": { + "email": { + "type": "string", + "example": null, + "nullable": true + }, + "full_name": { + "type": "string", + "example": null, + "nullable": true + }, + "username": { + "type": "string", + "example": "elastic", + "nullable": true + }, + "profile_uid": { + "type": "string", + "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" + } + }, + "required": [ + "email", + "full_name", + "username" + ] + }, + "owners": { + "type": "string", + "description": "The application that owns the cases: Stack Management, Observability, or Elastic Security.\n", + "enum": [ + "cases", + "observability", + "securitySolution" + ], + "example": "cases" + }, + "alert_comment_response_properties": { + "title": "Add case comment response properties for alerts", + "type": "object", + "required": [ + "type" + ], + "properties": { + "alertId": { + "type": "string", + "example": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-03-24T02:31:03.210Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null, + "nullable": true + }, + "full_name": { + "type": "string", + "example": null, + "nullable": true + }, + "username": { + "type": "string", + "example": "elastic", + "nullable": true + }, + "profile_uid": { + "type": "string", + "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" + } + } + }, + "id": { + "type": "string", + "example": "73362370-ab1a-11ec-985f-97e55adae8b9" + }, + "index": { + "type": "string", + "example": ".internal.alerts-security.alerts-default-000001" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "pushed_at": { + "type": "string", + "format": "date-time", + "example": null, + "nullable": true + }, + "pushed_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null, + "nullable": true + }, + "full_name": { + "type": "string", + "example": null, + "nullable": true + }, + "username": { + "type": "string", + "example": "elastic", + "nullable": true + }, + "profile_uid": { + "type": "string", + "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" + } + }, + "nullable": true + }, + "rule": { + "type": "object", + "properties": { + "id": { + "description": "The rule identifier.", + "type": "string", + "example": "94d80550-aaf4-11ec-985f-97e55adae8b9" + }, + "name": { + "description": "The rule name.", + "type": "string", + "example": "security_rule" + } + } + }, + "type": { + "type": "string", + "example": "alert", + "enum": [ + "alert" + ] + }, + "updated_at": { + "type": "string", + "format": "date-time", + "example": null + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null, + "nullable": true + }, + "full_name": { + "type": "string", + "example": null, + "nullable": true + }, + "username": { + "type": "string", + "example": "elastic", + "nullable": true + }, + "profile_uid": { + "type": "string", + "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" + } + } + }, + "version": { + "type": "string", + "example": "WzMwNDgsMV0=" + } + } + }, + "case_response_created_by_properties": { + "title": "Case response properties for created_by", + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null, + "nullable": true + }, + "full_name": { + "type": "string", + "example": null, + "nullable": true + }, + "username": { + "type": "string", + "example": "elastic", + "nullable": true + }, + "profile_uid": { + "type": "string", + "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" + } + }, + "required": [ + "email", + "full_name", + "username" + ] + }, + "case_response_pushed_by_properties": { + "title": "Case response properties for pushed_by", + "type": "object", + "nullable": true, + "properties": { + "email": { + "type": "string", + "example": null, + "nullable": true + }, + "full_name": { + "type": "string", + "example": null, + "nullable": true + }, + "username": { + "type": "string", + "example": "elastic", + "nullable": true + }, + "profile_uid": { + "type": "string", + "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" + } + }, + "required": [ + "email", + "full_name", + "username" + ] + }, + "case_response_updated_by_properties": { + "title": "Case response properties for updated_by", + "type": "object", + "nullable": true, + "properties": { + "email": { + "type": "string", + "example": null, + "nullable": true + }, + "full_name": { + "type": "string", + "example": null, + "nullable": true + }, + "username": { + "type": "string", + "example": "elastic", + "nullable": true + }, + "profile_uid": { + "type": "string", + "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" + } + }, + "required": [ + "email", + "full_name", + "username" + ] + }, + "user_comment_response_properties": { + "title": "Case response properties for user comments", + "type": "object", + "required": [ + "type" + ], + "properties": { + "comment": { + "type": "string", + "example": "A new comment." + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-05-13T09:16:17.416Z" + }, + "created_by": { + "$ref": "#/components/schemas/case_response_created_by_properties" + }, + "id": { + "type": "string", + "example": "8af6ac20-74f6-11ea-b83a-553aecdb28b6" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "pushed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "pushed_by": { + "$ref": "#/components/schemas/case_response_pushed_by_properties" + }, + "type": { + "type": "string", + "example": "user", + "enum": [ + "user" + ] + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "updated_by": { + "$ref": "#/components/schemas/case_response_updated_by_properties" + }, + "version": { + "type": "string", + "example": "WzIwNDMxLDFd" + } + } + }, + "case_response_connector_field_properties": { + "title": "Case response properties for connector fields", + "type": "object", + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } + }, + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } + } + }, + "connector_types": { + "type": "string", + "description": "The type of connector.", + "enum": [ + ".cases-webhook", + ".jira", + ".none", + ".resilient", + ".servicenow", + ".servicenow-sir", + ".swimlane" + ], + "example": ".none" + }, + "external_service": { + "type": "object", + "nullable": true, + "properties": { + "connector_id": { + "type": "string" + }, + "connector_name": { + "type": "string" + }, + "external_id": { + "type": "string" + }, + "external_title": { + "type": "string" + }, + "external_url": { + "type": "string" + }, + "pushed_at": { + "type": "string", + "format": "date-time" + }, + "pushed_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null, + "nullable": true + }, + "full_name": { + "type": "string", + "example": null, + "nullable": true + }, + "username": { + "type": "string", + "example": "elastic", + "nullable": true + }, + "profile_uid": { + "type": "string", + "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" + } + }, + "nullable": true + } + } + }, + "settings": { + "type": "object", + "description": "An object that contains the case settings.", + "properties": { + "syncAlerts": { + "description": "Turns alert syncing on or off.", + "type": "boolean", + "example": true + } + } + }, + "severity_property": { + "type": "string", + "description": "The severity of the case.", + "enum": [ + "critical", + "high", + "low", + "medium" + ], + "default": "low" + }, + "status": { + "type": "string", + "description": "The status of the case.", + "enum": [ + "closed", + "in-progress", + "open" + ] + }, + "case_response_properties": { + "title": "Case response properties", + "type": "object", + "required": [ + "closed_at", + "closed_by", + "comments", + "connector", + "created_at", + "created_by", + "description", + "duration", + "external_service", + "id", + "owner", + "settings", + "severity", + "status", + "tags", + "title", + "totalAlerts", + "totalComment", + "updated_at", + "updated_by", + "version" + ], + "properties": { + "closed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "closed_by": { + "$ref": "#/components/schemas/case_response_closed_by_properties" + }, + "comments": { + "title": "Case response properties for comments", + "description": "An array of comment objects for the case.", + "type": "array", + "items": { + "discriminator": { + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] + } + }, + "connector": { + "title": "Case response properties for connectors", + "type": "object", + "properties": { + "fields": { + "$ref": "#/components/schemas/case_response_connector_field_properties" + }, + "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-05-13T09:16:17.416Z" + }, + "created_by": { + "$ref": "#/components/schemas/case_response_created_by_properties" + }, + "description": { + "type": "string", + "example": "A case description." + }, + "duration": { + "type": "integer", + "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", + "nullable": true, + "example": 120 + }, + "external_service": { + "$ref": "#/components/schemas/external_service" + }, + "id": { + "type": "string", + "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "settings": { + "$ref": "#/components/schemas/settings" + }, + "severity": { + "$ref": "#/components/schemas/severity_property" + }, + "status": { + "$ref": "#/components/schemas/status" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "tag-1" + ] + }, + "title": { + "type": "string", + "example": "Case title 1" + }, + "totalAlerts": { + "type": "integer", + "example": 0 + }, + "totalComment": { + "type": "integer", + "example": 0 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "updated_by": { + "$ref": "#/components/schemas/case_response_updated_by_properties" + }, + "version": { + "type": "string", + "example": "WzUzMiwxXQ==" + } + } + }, + "alert_identifiers": { + "title": "Alert identifiers", + "description": "The alert identifier. It is required only when `type` is `alert`. If it is an array, `index` must also be an array with the same length or number of elements. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "x-technical-preview": true, + "example": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42" + }, + "alert_indices": { + "title": "Alert indices", + "description": "The alert index. It is required only when `type` is `alert`. If it is an array, `alertId` must also be an array with the same length or number of elements. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "x-technical-preview": true + }, + "rule": { + "title": "Alerting rule", + "description": "The rule that is associated with the alert. It is required only when `type` is `alert`. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "type": "object", + "x-technical-preview": true, + "properties": { + "id": { + "description": "The rule identifier.", + "type": "string", + "example": "94d80550-aaf4-11ec-985f-97e55adae8b9" + }, + "name": { + "description": "The rule name.", + "type": "string", + "example": "security_rule" + } + } + }, + "add_alert_comment_request_properties": { + "title": "Add case comment request properties for alerts", + "required": [ + "alertId", + "index", + "owner", + "rule", + "type" + ], + "description": "Defines properties for case comment requests when type is alert.", + "type": "object", + "properties": { + "alertId": { + "$ref": "#/components/schemas/alert_identifiers" + }, + "index": { + "$ref": "#/components/schemas/alert_indices" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "rule": { + "$ref": "#/components/schemas/rule" + }, + "type": { + "description": "The type of comment.", + "type": "string", + "example": "alert", + "enum": [ + "alert" + ] + } + } + }, + "add_user_comment_request_properties": { + "title": "Add case comment request properties for user comments", + "description": "Defines properties for case comment requests when type is user.", + "type": "object", + "properties": { + "comment": { + "description": "The new comment. It is required only when `type` is `user`.", + "type": "string", + "example": "A new comment." + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "type": { + "type": "string", + "description": "The type of comment.", + "example": "user", + "enum": [ + "user" + ] + } + }, + "required": [ + "comment", + "owner", + "type" + ] + }, + "add_case_comment_request": { + "title": "Add case comment request", + "description": "The add comment to case API request body varies depending on whether you are adding an alert or a comment.", + "discriminator": { + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/add_alert_comment_request_properties" + }, + { + "$ref": "#/components/schemas/add_user_comment_request_properties" + } + ] + }, + "update_alert_comment_request_properties": { + "title": "Update case comment request properties for alerts", + "description": "Defines properties for case comment requests when type is alert.", + "required": [ + "alertId", + "id", + "index", + "owner", + "rule", + "type", + "version" + ], + "type": "object", + "properties": { + "alertId": { + "$ref": "#/components/schemas/alert_identifiers" + }, + "id": { + "type": "string", + "description": "The identifier for the comment. To retrieve comment IDs, use the get comments API.\n", + "example": "8af6ac20-74f6-11ea-b83a-553aecdb28b6" + }, + "index": { + "$ref": "#/components/schemas/alert_indices" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "rule": { + "$ref": "#/components/schemas/rule" + }, + "type": { + "description": "The type of comment.", + "type": "string", + "enum": [ + "alert" + ], + "example": "alert" + }, + "version": { + "description": "The current comment version. To retrieve version values, use the get comments API.\n", + "type": "string", + "example": "Wzk1LDFd" + } + } + }, + "update_user_comment_request_properties": { + "title": "Update case comment request properties for user comments", + "description": "Defines properties for case comment requests when type is user.", + "type": "object", + "properties": { + "comment": { + "description": "The new comment. It is required only when `type` is `user`.", + "type": "string", + "example": "A new comment." + }, + "id": { + "type": "string", + "description": "The identifier for the comment. To retrieve comment IDs, use the get comments API.\n", + "example": "8af6ac20-74f6-11ea-b83a-553aecdb28b6" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "type": { + "type": "string", + "description": "The type of comment.", + "enum": [ + "user" + ], + "example": "user" + }, + "version": { + "description": "The current comment version. To retrieve version values, use the get comments API.\n", + "type": "string", + "example": "Wzk1LDFd" + } + }, + "required": [ + "comment", + "id", + "owner", + "type", + "version" + ] + }, + "update_case_comment_request": { + "title": "Update case comment request", + "description": "The update case comment API request body varies depending on whether you are updating an alert or a comment.", + "discriminator": { + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/update_alert_comment_request_properties" + }, + { + "$ref": "#/components/schemas/update_user_comment_request_properties" + } + ] + } + }, + "examples": { + "add_comment_request": { + "summary": "Adds a comment to a case.", + "value": { + "type": "user", + "comment": "A new comment.", + "owner": "cases" + } + }, + "add_comment_response": { + "summary": "The add comment to case API returns a JSON object that contains details about the case and its comments.", + "value": { + "comments": [ + { + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNDMxLDFd", + "type": "user", + "owner": "cases", + "comment": "A new comment.", + "created_at": "2022-06-02T00:49:47.716Z", + "created_by": { + "username": "elastic", + "email": null, + "full_name": null + } + } + ], + "totalAlerts": 0, + "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIzMzgsMV0=", + "totalComment": 1, + "title": "Case title 1", + "tags": [ + "tag 1" + ], + "description": "A case description.", + "settings": { + "syncAlerts": false + }, + "owner": "cases", + "duration": null, + "severity": "low", + "closed_at": null, + "closed_by": null, + "created_at": "2022-03-24T00:37:03.906Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "status": "open", + "updated_at": "2022-06-03T00:49:47.716Z", + "updated_by": { + "username": "elastic", + "email": null, + "full_name": null + }, + "connector": { + "id": "none", + "name": "none", + "type": ".none", + "fields": null + }, + "external_service": null + } + }, + "update_comment_request": { + "summary": "Updates a comment of a case.", + "value": { + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "Wzk1LDFd", + "type": "user", + "comment": "An updated comment.", + "owner": "cases" + } + }, + "update_comment_response": { + "summary": "The add comment to case API returns a JSON object that contains details about the case and its comments.", + "value": { + "comments": [ + { + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNjM3LDFd", + "comment": "An updated comment.", + "type": "user", + "owner": "cases", + "created_at": "2022-03-24T00:37:10.832Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": "2022-03-24T01:27:06.210Z", + "updated_by": { + "username": "elastic", + "full_name": null, + "email": null + } + } + ], + "totalAlerts": 0, + "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNjM2LDFd", + "totalComment": 1, + "title": "Case title 1", + "tags": [ + "tag 1" + ], + "description": "A case description.", + "settings": { + "syncAlerts": false + }, + "owner": "cases", + "duration": null, + "severity": "low", + "closed_at": null, + "closed_by": null, + "created_at": "2022-03-24T00:37:03.906Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "status": "open", + "updated_at": "2022-03-24T01:27:06.210Z", + "updated_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "connector": { + "id": "none", + "name": "none", + "type": ".none", + "fields": null + }, + "external_service": null + } + } + } + }, + "security": [ + { + "basicAuth": [] + }, + { + "apiKeyAuth": [] + } + ] +} \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/bundled-min.yaml b/x-pack/plugins/cases/docs/openapi/bundled-min.yaml new file mode 100644 index 000000000000..75f982011a13 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/bundled-min.yaml @@ -0,0 +1,923 @@ +openapi: 3.0.1 +info: + title: Cases + description: OpenAPI schema for Cases endpoints + version: '0.2' + contact: + name: Cases Team + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +tags: + - name: cases + description: Case APIs enable you to open and track issues. +servers: + - url: http://localhost:5601 + description: local +paths: + /s/{spaceId}/api/cases/{caseId}/comments: + post: + summary: Adds a comment or alert to a case. + operationId: addCaseComment + description: | + You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating. + tags: + - cases + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/case_id' + - $ref: '#/components/parameters/space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/add_case_comment_request' + examples: + createCaseCommentRequest: + $ref: '#/components/examples/add_comment_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/case_response_properties' + examples: + createCaseCommentResponse: + $ref: '#/components/examples/add_comment_response' + servers: + - url: https://localhost:5601 + delete: + summary: Deletes all comments and alerts from a case. + operationId: deleteCaseComments + description: | + You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting. + tags: + - cases + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/case_id' + - $ref: '#/components/parameters/space_id' + responses: + '204': + description: Indicates a successful call. + servers: + - url: https://localhost:5601 + patch: + summary: Updates a comment or alert in a case. + operationId: updateCaseComment + description: | + You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment. + tags: + - cases + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/case_id' + - $ref: '#/components/parameters/space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/update_case_comment_request' + examples: + updateCaseCommentRequest: + $ref: '#/components/examples/update_comment_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/case_response_properties' + examples: + updateCaseCommentResponse: + $ref: '#/components/examples/update_comment_response' + servers: + - url: https://localhost:5601 + get: + summary: Retrieves all the comments from a case. + operationId: getAllCaseComments + description: | + You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking. + deprecated: true + tags: + - cases + parameters: + - $ref: '#/components/parameters/case_id' + - $ref: '#/components/parameters/space_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/case_response_properties' + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + apiKeyAuth: + type: apiKey + in: header + name: ApiKey + parameters: + case_id: + in: path + name: caseId + description: The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. + required: true + schema: + type: string + example: 9c235210-6834-11ea-a78c-6ffb38a34414 + space_id: + in: path + name: spaceId + description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. + required: true + schema: + type: string + example: default + kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + required: true + schemas: + case_response_closed_by_properties: + title: Case response properties for closed_by + type: object + nullable: true + properties: + email: + type: string + example: null + nullable: true + full_name: + type: string + example: null + nullable: true + username: + type: string + example: elastic + nullable: true + profile_uid: + type: string + example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 + required: + - email + - full_name + - username + owners: + type: string + description: | + The application that owns the cases: Stack Management, Observability, or Elastic Security. + enum: + - cases + - observability + - securitySolution + example: cases + alert_comment_response_properties: + title: Add case comment response properties for alerts + type: object + required: + - type + properties: + alertId: + type: string + example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + created_at: + type: string + format: date-time + example: '2022-03-24T02:31:03.210Z' + created_by: + type: object + properties: + email: + type: string + example: null + nullable: true + full_name: + type: string + example: null + nullable: true + username: + type: string + example: elastic + nullable: true + profile_uid: + type: string + example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 + id: + type: string + example: 73362370-ab1a-11ec-985f-97e55adae8b9 + index: + type: string + example: .internal.alerts-security.alerts-default-000001 + owner: + $ref: '#/components/schemas/owners' + pushed_at: + type: string + format: date-time + example: null + nullable: true + pushed_by: + type: object + properties: + email: + type: string + example: null + nullable: true + full_name: + type: string + example: null + nullable: true + username: + type: string + example: elastic + nullable: true + profile_uid: + type: string + example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 + nullable: true + rule: + type: object + properties: + id: + description: The rule identifier. + type: string + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + description: The rule name. + type: string + example: security_rule + type: + type: string + example: alert + enum: + - alert + updated_at: + type: string + format: date-time + example: null + updated_by: + type: object + properties: + email: + type: string + example: null + nullable: true + full_name: + type: string + example: null + nullable: true + username: + type: string + example: elastic + nullable: true + profile_uid: + type: string + example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 + version: + type: string + example: WzMwNDgsMV0= + case_response_created_by_properties: + title: Case response properties for created_by + type: object + properties: + email: + type: string + example: null + nullable: true + full_name: + type: string + example: null + nullable: true + username: + type: string + example: elastic + nullable: true + profile_uid: + type: string + example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 + required: + - email + - full_name + - username + case_response_pushed_by_properties: + title: Case response properties for pushed_by + type: object + nullable: true + properties: + email: + type: string + example: null + nullable: true + full_name: + type: string + example: null + nullable: true + username: + type: string + example: elastic + nullable: true + profile_uid: + type: string + example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 + required: + - email + - full_name + - username + case_response_updated_by_properties: + title: Case response properties for updated_by + type: object + nullable: true + properties: + email: + type: string + example: null + nullable: true + full_name: + type: string + example: null + nullable: true + username: + type: string + example: elastic + nullable: true + profile_uid: + type: string + example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 + required: + - email + - full_name + - username + user_comment_response_properties: + title: Case response properties for user comments + type: object + required: + - type + properties: + comment: + type: string + example: A new comment. + created_at: + type: string + format: date-time + example: '2022-05-13T09:16:17.416Z' + created_by: + $ref: '#/components/schemas/case_response_created_by_properties' + id: + type: string + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + owner: + $ref: '#/components/schemas/owners' + pushed_at: + type: string + format: date-time + nullable: true + example: null + pushed_by: + $ref: '#/components/schemas/case_response_pushed_by_properties' + type: + type: string + example: user + enum: + - user + updated_at: + type: string + format: date-time + nullable: true + example: null + updated_by: + $ref: '#/components/schemas/case_response_updated_by_properties' + version: + type: string + example: WzIwNDMxLDFd + case_response_connector_field_properties: + title: Case response properties for connector fields + type: object + description: An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value. + nullable: true + properties: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: + description: The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors. + type: string + destIp: + description: A comma-separated list of destination IPs for ServiceNow SecOps connectors. + type: string + impact: + description: The effect an incident had on business for ServiceNow ITSM connectors. + type: string + issueType: + description: The type of issue for Jira connectors. + type: string + issueTypes: + description: The type of incident for IBM Resilient connectors. + type: array + items: + type: number + malwareHash: + description: A comma-separated list of malware hashes for ServiceNow SecOps connectors. + type: string + malwareUrl: + description: A comma-separated list of malware URLs for ServiceNow SecOps connectors. + type: string + parent: + description: The key of the parent issue, when the issue type is sub-task for Jira connectors. + type: string + priority: + description: The priority of the issue for Jira and ServiceNow SecOps connectors. + type: string + severity: + description: The severity of the incident for ServiceNow ITSM connectors. + type: string + severityCode: + description: The severity code of the incident for IBM Resilient connectors. + type: number + sourceIp: + description: A comma-separated list of source IPs for ServiceNow SecOps connectors. + type: string + subcategory: + description: The subcategory of the incident for ServiceNow ITSM connectors. + type: string + urgency: + description: The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors. + type: string + connector_types: + type: string + description: The type of connector. + enum: + - .cases-webhook + - .jira + - .none + - .resilient + - .servicenow + - .servicenow-sir + - .swimlane + example: .none + external_service: + type: object + nullable: true + properties: + connector_id: + type: string + connector_name: + type: string + external_id: + type: string + external_title: + type: string + external_url: + type: string + pushed_at: + type: string + format: date-time + pushed_by: + type: object + properties: + email: + type: string + example: null + nullable: true + full_name: + type: string + example: null + nullable: true + username: + type: string + example: elastic + nullable: true + profile_uid: + type: string + example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 + nullable: true + settings: + type: object + description: An object that contains the case settings. + properties: + syncAlerts: + description: Turns alert syncing on or off. + type: boolean + example: true + severity_property: + type: string + description: The severity of the case. + enum: + - critical + - high + - low + - medium + default: low + status: + type: string + description: The status of the case. + enum: + - closed + - in-progress + - open + case_response_properties: + title: Case response properties + type: object + required: + - closed_at + - closed_by + - comments + - connector + - created_at + - created_by + - description + - duration + - external_service + - id + - owner + - settings + - severity + - status + - tags + - title + - totalAlerts + - totalComment + - updated_at + - updated_by + - version + properties: + closed_at: + type: string + format: date-time + nullable: true + closed_by: + $ref: '#/components/schemas/case_response_closed_by_properties' + comments: + title: Case response properties for comments + description: An array of comment objects for the case. + type: array + items: + discriminator: + propertyName: type + oneOf: + - $ref: '#/components/schemas/alert_comment_response_properties' + - $ref: '#/components/schemas/user_comment_response_properties' + connector: + title: Case response properties for connectors + type: object + properties: + fields: + $ref: '#/components/schemas/case_response_connector_field_properties' + id: + description: The identifier for the connector. To create a case without a connector, use `none`. + type: string + example: none + name: + description: The name of the connector. To create a case without a connector, use `none`. + type: string + example: none + type: + $ref: '#/components/schemas/connector_types' + created_at: + type: string + format: date-time + example: '2022-05-13T09:16:17.416Z' + created_by: + $ref: '#/components/schemas/case_response_created_by_properties' + description: + type: string + example: A case description. + duration: + type: integer + description: | + The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero. + nullable: true + example: 120 + external_service: + $ref: '#/components/schemas/external_service' + id: + type: string + example: 66b9aa00-94fa-11ea-9f74-e7e108796192 + owner: + $ref: '#/components/schemas/owners' + settings: + $ref: '#/components/schemas/settings' + severity: + $ref: '#/components/schemas/severity_property' + status: + $ref: '#/components/schemas/status' + tags: + type: array + items: + type: string + example: + - tag-1 + title: + type: string + example: Case title 1 + totalAlerts: + type: integer + example: 0 + totalComment: + type: integer + example: 0 + updated_at: + type: string + format: date-time + nullable: true + updated_by: + $ref: '#/components/schemas/case_response_updated_by_properties' + version: + type: string + example: WzUzMiwxXQ== + alert_identifiers: + title: Alert identifiers + description: | + The alert identifier. It is required only when `type` is `alert`. If it is an array, `index` must also be an array with the same length or number of elements. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + oneOf: + - type: string + - type: array + items: + type: string + x-technical-preview: true + example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + alert_indices: + title: Alert indices + description: | + The alert index. It is required only when `type` is `alert`. If it is an array, `alertId` must also be an array with the same length or number of elements. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + oneOf: + - type: string + - type: array + items: + type: string + x-technical-preview: true + rule: + title: Alerting rule + description: | + The rule that is associated with the alert. It is required only when `type` is `alert`. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + type: object + x-technical-preview: true + properties: + id: + description: The rule identifier. + type: string + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + description: The rule name. + type: string + example: security_rule + add_alert_comment_request_properties: + title: Add case comment request properties for alerts + required: + - alertId + - index + - owner + - rule + - type + description: Defines properties for case comment requests when type is alert. + type: object + properties: + alertId: + $ref: '#/components/schemas/alert_identifiers' + index: + $ref: '#/components/schemas/alert_indices' + owner: + $ref: '#/components/schemas/owners' + rule: + $ref: '#/components/schemas/rule' + type: + description: The type of comment. + type: string + example: alert + enum: + - alert + add_user_comment_request_properties: + title: Add case comment request properties for user comments + description: Defines properties for case comment requests when type is user. + type: object + properties: + comment: + description: The new comment. It is required only when `type` is `user`. + type: string + example: A new comment. + owner: + $ref: '#/components/schemas/owners' + type: + type: string + description: The type of comment. + example: user + enum: + - user + required: + - comment + - owner + - type + add_case_comment_request: + title: Add case comment request + description: The add comment to case API request body varies depending on whether you are adding an alert or a comment. + discriminator: + propertyName: type + oneOf: + - $ref: '#/components/schemas/add_alert_comment_request_properties' + - $ref: '#/components/schemas/add_user_comment_request_properties' + update_alert_comment_request_properties: + title: Update case comment request properties for alerts + description: Defines properties for case comment requests when type is alert. + required: + - alertId + - id + - index + - owner + - rule + - type + - version + type: object + properties: + alertId: + $ref: '#/components/schemas/alert_identifiers' + id: + type: string + description: | + The identifier for the comment. To retrieve comment IDs, use the get comments API. + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + index: + $ref: '#/components/schemas/alert_indices' + owner: + $ref: '#/components/schemas/owners' + rule: + $ref: '#/components/schemas/rule' + type: + description: The type of comment. + type: string + enum: + - alert + example: alert + version: + description: | + The current comment version. To retrieve version values, use the get comments API. + type: string + example: Wzk1LDFd + update_user_comment_request_properties: + title: Update case comment request properties for user comments + description: Defines properties for case comment requests when type is user. + type: object + properties: + comment: + description: The new comment. It is required only when `type` is `user`. + type: string + example: A new comment. + id: + type: string + description: | + The identifier for the comment. To retrieve comment IDs, use the get comments API. + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + owner: + $ref: '#/components/schemas/owners' + type: + type: string + description: The type of comment. + enum: + - user + example: user + version: + description: | + The current comment version. To retrieve version values, use the get comments API. + type: string + example: Wzk1LDFd + required: + - comment + - id + - owner + - type + - version + update_case_comment_request: + title: Update case comment request + description: The update case comment API request body varies depending on whether you are updating an alert or a comment. + discriminator: + propertyName: type + oneOf: + - $ref: '#/components/schemas/update_alert_comment_request_properties' + - $ref: '#/components/schemas/update_user_comment_request_properties' + examples: + add_comment_request: + summary: Adds a comment to a case. + value: + type: user + comment: A new comment. + owner: cases + add_comment_response: + summary: The add comment to case API returns a JSON object that contains details about the case and its comments. + value: + comments: + - id: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNDMxLDFd + type: user + owner: cases + comment: A new comment. + created_at: '2022-06-02T00:49:47.716Z' + created_by: + username: elastic + email: null + full_name: null + totalAlerts: 0 + id: 293f1bc0-74f6-11ea-b83a-553aecdb28b6 + version: WzIzMzgsMV0= + totalComment: 1 + title: Case title 1 + tags: + - tag 1 + description: A case description. + settings: + syncAlerts: false + owner: cases + duration: null + severity: low + closed_at: null + closed_by: null + created_at: '2022-03-24T00:37:03.906Z' + created_by: + username: elastic + full_name: null + email: null + status: open + updated_at: '2022-06-03T00:49:47.716Z' + updated_by: + username: elastic + email: null + full_name: null + connector: + id: none + name: none + type: .none + fields: null + external_service: null + update_comment_request: + summary: Updates a comment of a case. + value: + id: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + version: Wzk1LDFd + type: user + comment: An updated comment. + owner: cases + update_comment_response: + summary: The add comment to case API returns a JSON object that contains details about the case and its comments. + value: + comments: + - id: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNjM3LDFd + comment: An updated comment. + type: user + owner: cases + created_at: '2022-03-24T00:37:10.832Z' + created_by: + username: elastic + full_name: null + email: null + pushed_at: null + pushed_by: null + updated_at: '2022-03-24T01:27:06.210Z' + updated_by: + username: elastic + full_name: null + email: null + totalAlerts: 0 + id: 293f1bc0-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNjM2LDFd + totalComment: 1 + title: Case title 1 + tags: + - tag 1 + description: A case description. + settings: + syncAlerts: false + owner: cases + duration: null + severity: low + closed_at: null + closed_by: null + created_at: '2022-03-24T00:37:03.906Z' + created_by: + username: elastic + full_name: null + email: null + status: open + updated_at: '2022-03-24T01:27:06.210Z' + updated_by: + username: elastic + full_name: null + email: null + connector: + id: none + name: none + type: .none + fields: null + external_service: null +security: + - basicAuth: [] + - apiKeyAuth: [] diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_response.yaml index ea825da377a3..382629e4fab0 100644 --- a/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_response.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_response.yaml @@ -1,58 +1,46 @@ summary: The add comment to case API returns a JSON object that contains details about the case and its comments. value: - { - "comments":[ - { - "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", - "version": "WzIwNDMxLDFd", - "type":"user", - "owner":"cases", - "comment":"A new comment.", - "created_at":"2022-06-02T00:49:47.716Z", - "created_by": { - "username": "elastic", - "email": null, - "full_name": null - }, - "pushed_at":null, - "pushed_by":null, - "updated_at":null, - "updated_by":null - } - ], - "totalAlerts":0, - "id":"293f1bc0-74f6-11ea-b83a-553aecdb28b6", - "version":"WzIzMzgsMV0=", - "totalComment":1, - "title": "Case title 1", - "tags": ["tag 1"], - "description": "A case description.", - "settings": { - "syncAlerts":false - }, - "owner": "cases", - "duration": null, - "severity": "low", - "closed_at": null, - "closed_by": null, - "created_at": "2022-03-24T00:37:03.906Z", - "created_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "status": "open", - "updated_at": "2022-06-03T00:49:47.716Z", - "updated_by": { - "username": "elastic", - "email": null, - "full_name": null - }, - "connector": { - "id": "none", - "name": "none", - "type": ".none", - "fields": null - }, - "external_service": null - } \ No newline at end of file + comments: + - id: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNDMxLDFd + type: user + owner: cases + comment: A new comment. + created_at: '2022-06-02T00:49:47.716Z' + created_by: + username: elastic + email: null + full_name: null + totalAlerts: 0 + id: 293f1bc0-74f6-11ea-b83a-553aecdb28b6 + version: WzIzMzgsMV0= + totalComment: 1 + title: Case title 1 + tags: + - tag 1 + description: A case description. + settings: + syncAlerts: false + owner: cases + duration: null + severity: low + closed_at: null + closed_by: null + created_at: '2022-03-24T00:37:03.906Z' + created_by: + username: elastic + full_name: null + email: null + status: open + updated_at: '2022-06-03T00:49:47.716Z' + updated_by: + username: elastic + email: null + full_name: null + connector: + id: none + name: none + type: .none + fields: null + external_service: null + \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_request.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_request.yaml index 066830ce2077..e09bb8ad35f2 100644 --- a/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_request.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_request.yaml @@ -4,5 +4,6 @@ value: "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", "version": "Wzk1LDFd", "type": "user", - "comment": "An updated comment." + "comment": "An updated comment.", + "owner": "cases" } \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_response.yaml index 9a3ba642d6ec..4c81e759a92f 100644 --- a/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_response.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_response.yaml @@ -1,59 +1,52 @@ summary: The add comment to case API returns a JSON object that contains details about the case and its comments. value: - { - "comments":[{ - "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", - "version": "WzIwNjM3LDFd", - "comment": "An updated comment.", - "type": "user", - "owner": "cases", - "created_at": "2022-03-24T00:37:10.832Z", - "created_by": { - "username": "elastic", - "full_name": null, - "email": null - }, - "pushed_at": null, - "pushed_by": null, - "updated_at": "2022-03-24T01:27:06.210Z", - "updated_by": { - "username": "elastic", - "full_name": null, - "email": null - } - } - ], - "totalAlerts": 0, - "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", - "version": "WzIwNjM2LDFd", - "totalComment": 1, - "title": "Case title 1", - "tags": ["tag 1"], - "description": "A case description.", - "settings": {"syncAlerts":false}, - "owner": "cases", - "duration": null, - "severity": "low", - "closed_at": null, - "closed_by": null, - "created_at": "2022-03-24T00:37:03.906Z", - "created_by": { - "username": "elastic", - "full_name": null, - "email": null - }, - "status": "open", - "updated_at": "2022-03-24T01:27:06.210Z", - "updated_by": { - "username": "elastic", - "full_name": null, - "email": null - }, - "connector": { - "id": "none", - "name": "none", - "type": ".none", - "fields": null - }, - "external_service": null - } + comments: + - id: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNjM3LDFd + comment: An updated comment. + type: user + owner: cases + created_at: '2022-03-24T00:37:10.832Z' + created_by: + username: elastic + full_name: null + email: null + pushed_at: null + pushed_by: null + updated_at: '2022-03-24T01:27:06.210Z' + updated_by: + username: elastic + full_name: null + email: null + totalAlerts: 0 + id: 293f1bc0-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNjM2LDFd + totalComment: 1 + title: Case title 1 + tags: + - tag 1 + description: A case description. + settings: + syncAlerts: false + owner: cases + duration: null + severity: low + closed_at: null + closed_by: null + created_at: '2022-03-24T00:37:03.906Z' + created_by: + username: elastic + full_name: null + email: null + status: open + updated_at: '2022-03-24T01:27:06.210Z' + updated_by: + username: elastic + full_name: null + email: null + connector: + id: none + name: none + type: .none + fields: null + external_service: null diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/case_id.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/case_id.yaml index beae115acce0..eebde8582374 100644 --- a/x-pack/plugins/cases/docs/openapi/components/parameters/case_id.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/parameters/case_id.yaml @@ -4,4 +4,4 @@ description: The identifier for the case. To retrieve case IDs, use the find cas required: true schema: type: string - example: '9c235210-6834-11ea-a78c-6ffb38a34414' \ No newline at end of file + example: 9c235210-6834-11ea-a78c-6ffb38a34414 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/add_alert_comment_request_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/add_alert_comment_request_properties.yaml index ec2e69f66c1e..c99ebb19cc81 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/add_alert_comment_request_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/add_alert_comment_request_properties.yaml @@ -1,55 +1,24 @@ +title: Add case comment request properties for alerts +required: + - alertId + - index + - owner + - rule + - type +description: Defines properties for case comment requests when type is alert. type: object properties: - alertId: - description: > - The alert identifier. It is required only when `type` is `alert`. If it is - an array, `index` must also be an array. This functionality is in - technical preview and may be changed or removed in a future release. - Elastic will apply best effort to fix any issues, but features in - technical preview are not subject to the support SLA of official GA - features. - oneOf: - - type: string - - type: array - items: - type: string - x-technical-preview: true - example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + alertId: + $ref: 'alert_identifiers.yaml' index: - description: > - The alert index. It is required only when `type` is `alert`. If it is an - array, `alertId` must also be an array. This functionality is in technical - preview and may be changed or removed in a future release. Elastic will - apply best effort to fix any issues, but features in technical preview are - not subject to the support SLA of official GA features. - oneOf: - - type: string - - type: array - items: - type: string - x-technical-preview: true + $ref: 'alert_indices.yaml' owner: $ref: 'owners.yaml' rule: - description: > - The rule that is associated with the alert. It is required only when - `type` is `alert`. This functionality is in technical preview and may be - changed or removed in a future release. Elastic will apply best effort to - fix any issues, but features in technical preview are not subject to the - support SLA of official GA features. - type: object - x-technical-preview: true - properties: - $ref: 'rule_properties.yaml' + $ref: 'rule.yaml' type: description: The type of comment. type: string - enum: - - alert example: alert -required: - - alertId - - index - - owner - - rule - - type \ No newline at end of file + enum: + - alert \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/add_case_comment_request.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/add_case_comment_request.yaml new file mode 100644 index 000000000000..70ed1324fde9 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/add_case_comment_request.yaml @@ -0,0 +1,9 @@ +title: Add case comment request +description: >- + The add comment to case API request body varies depending on whether you are + adding an alert or a comment. +discriminator: + propertyName: type +oneOf: + - $ref: 'add_alert_comment_request_properties.yaml' + - $ref: 'add_user_comment_request_properties.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/add_user_comment_request_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/add_user_comment_request_properties.yaml index d09958e13fec..40efb7f945f4 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/add_user_comment_request_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/add_user_comment_request_properties.yaml @@ -1,3 +1,5 @@ +title: Add case comment request properties for user comments +description: Defines properties for case comment requests when type is user. type: object properties: comment: @@ -9,9 +11,9 @@ properties: type: type: string description: The type of comment. + example: user enum: - user - example: user required: - comment - owner diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml index 4fcbfe5527e9..056abad0a300 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml @@ -1,5 +1,7 @@ - +title: Add case comment response properties for alerts type: object +required: + - type properties: alertId: type: string @@ -37,6 +39,8 @@ properties: type: type: string example: alert + enum: + - alert updated_at: type: string format: date-time diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/alert_identifiers.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_identifiers.yaml new file mode 100644 index 000000000000..4a470d8ff447 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_identifiers.yaml @@ -0,0 +1,15 @@ +title: Alert identifiers +description: > + The alert identifier. It is required only when `type` is `alert`. If it is + an array, `index` must also be an array with the same length or number of + elements. This functionality is in technical preview and may be changed or + removed in a future release. Elastic will apply best effort to fix any issues, + but features in technical preview are not subject to the support SLA of + official GA features. +oneOf: + - type: string + - type: array + items: + type: string +x-technical-preview: true +example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/alert_indices.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_indices.yaml new file mode 100644 index 000000000000..b265f4ee8ce3 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_indices.yaml @@ -0,0 +1,14 @@ +title: Alert indices +description: > + The alert index. It is required only when `type` is `alert`. If it is an + array, `alertId` must also be an array with the same length or number of + elements. This functionality is in technical preview and may be changed or + removed in a future release. Elastic will apply best effort to fix any issues, + but features in technical preview are not subject to the support SLA of + official GA features. +oneOf: + - type: string + - type: array + items: + type: string +x-technical-preview: true \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_closed_by_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_closed_by_properties.yaml new file mode 100644 index 000000000000..95bd14e4957a --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_closed_by_properties.yaml @@ -0,0 +1,9 @@ +title: Case response properties for closed_by +type: object +nullable: true +properties: + $ref: 'user_properties.yaml' +required: + - email + - full_name + - username \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_connector_field_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_connector_field_properties.yaml new file mode 100644 index 000000000000..1f8aa7c90190 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_connector_field_properties.yaml @@ -0,0 +1,52 @@ +title: Case response properties for connector fields +type: object +description: An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value. +nullable: true +properties: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: + description: The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors. + type: string + destIp: + description: A comma-separated list of destination IPs for ServiceNow SecOps connectors. + type: string + impact: + description: The effect an incident had on business for ServiceNow ITSM connectors. + type: string + issueType: + description: The type of issue for Jira connectors. + type: string + issueTypes: + description: The type of incident for IBM Resilient connectors. + type: array + items: + type: number + malwareHash: + description: A comma-separated list of malware hashes for ServiceNow SecOps connectors. + type: string + malwareUrl: + description: A comma-separated list of malware URLs for ServiceNow SecOps connectors. + type: string + parent: + description: The key of the parent issue, when the issue type is sub-task for Jira connectors. + type: string + priority: + description: The priority of the issue for Jira and ServiceNow SecOps connectors. + type: string + severity: + description: The severity of the incident for ServiceNow ITSM connectors. + type: string + severityCode: + description: The severity code of the incident for IBM Resilient connectors. + type: number + sourceIp: + description: A comma-separated list of source IPs for ServiceNow SecOps connectors. + type: string + subcategory: + description: The subcategory of the incident for ServiceNow ITSM connectors. + type: string + urgency: + description: The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors. + type: string \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_created_by_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_created_by_properties.yaml new file mode 100644 index 000000000000..a58d7ff57386 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_created_by_properties.yaml @@ -0,0 +1,8 @@ +title: Case response properties for created_by +type: object +properties: + $ref: 'user_properties.yaml' +required: + - email + - full_name + - username \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml index 25f629658519..1be19845c56a 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml @@ -1,82 +1,112 @@ -closed_at: - type: string - format: date-time - nullable: true - example: null -closed_by: - type: object - properties: - $ref: 'user_properties.yaml' - nullable: true - example: null -comments: - type: array - items: - oneOf: - - $ref: 'alert_comment_response_properties.yaml' - - $ref: 'user_comment_response_properties.yaml' - example: [] -connector: - type: object - properties: - $ref: 'connector_properties.yaml' -created_at: - type: string - format: date-time - example: 2022-05-13T09:16:17.416Z -created_by: - type: object - properties: - $ref: 'user_properties.yaml' -description: - type: string - example: "A case description." -duration: - type: integer - description: > - The elapsed time from the creation of the case to its closure (in seconds). - If the case has not been closed, the duration is set to null. If the case - was closed after less than half a second, the duration is rounded down to - zero. - example: 120 -external_service: - $ref: 'external_service.yaml' -id: - type: string - example: 66b9aa00-94fa-11ea-9f74-e7e108796192 -owner: - $ref: 'owners.yaml' -settings: - $ref: 'settings.yaml' -severity: - $ref: 'severity_property.yaml' -status: - $ref: 'status.yaml' -tags: - type: array - items: +title: Case response properties +type: object +required: + - closed_at + - closed_by + - comments + - connector + - created_at + - created_by + - description + - duration + - external_service + - id + - owner + - settings + - severity + - status + - tags + - title + - totalAlerts + - totalComment + - updated_at + - updated_by + - version +properties: + closed_at: type: string - example: ["tag-1"] -title: - type: string - example: Case title 1 -totalAlerts: - type: integer - example: 0 -totalComment: - type: integer - example: 0 -updated_at: - type: string - format: date-time - nullable: true - example: null -updated_by: - type: object - properties: - $ref: 'user_properties.yaml' - nullable: true - example: null -version: - type: string - example: WzUzMiwxXQ== + format: date-time + nullable: true + closed_by: + $ref: 'case_response_closed_by_properties.yaml' + comments: + title: Case response properties for comments + description: An array of comment objects for the case. + type: array + items: + discriminator: + propertyName: type + oneOf: + - $ref: 'alert_comment_response_properties.yaml' + - $ref: 'user_comment_response_properties.yaml' + connector: + title: Case response properties for connectors + type: object + properties: + fields: + $ref: 'case_response_connector_field_properties.yaml' + id: + description: The identifier for the connector. To create a case without a connector, use `none`. + type: string + example: none + name: + description: The name of the connector. To create a case without a connector, use `none`. + type: string + example: none + type: + $ref: 'connector_types.yaml' + created_at: + type: string + format: date-time + example: '2022-05-13T09:16:17.416Z' + created_by: + $ref: 'case_response_created_by_properties.yaml' + description: + type: string + example: A case description. + duration: + type: integer + description: > + The elapsed time from the creation of the case to its closure (in seconds). + If the case has not been closed, the duration is set to null. If the case + was closed after less than half a second, the duration is rounded down to + zero. + nullable: true + example: 120 + external_service: + $ref: 'external_service.yaml' + id: + type: string + example: 66b9aa00-94fa-11ea-9f74-e7e108796192 + owner: + $ref: 'owners.yaml' + settings: + $ref: 'settings.yaml' + severity: + $ref: 'severity_property.yaml' + status: + $ref: 'status.yaml' + tags: + type: array + items: + type: string + example: + - tag-1 + title: + type: string + example: Case title 1 + totalAlerts: + type: integer + example: 0 + totalComment: + type: integer + example: 0 + updated_at: + type: string + format: date-time + nullable: true + updated_by: + $ref: 'case_response_updated_by_properties.yaml' + version: + type: string + example: WzUzMiwxXQ== diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_pushed_by_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_pushed_by_properties.yaml new file mode 100644 index 000000000000..c59a5565c98b --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_pushed_by_properties.yaml @@ -0,0 +1,9 @@ +title: Case response properties for pushed_by +type: object +nullable: true +properties: + $ref: 'user_properties.yaml' +required: + - email + - full_name + - username \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_updated_by_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_updated_by_properties.yaml new file mode 100644 index 000000000000..cd1bae033f2f --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_updated_by_properties.yaml @@ -0,0 +1,9 @@ +title: Case response properties for updated_by +type: object +nullable: true +properties: + $ref: 'user_properties.yaml' +required: + - email + - full_name + - username \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/external_service.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/external_service.yaml index 950a2bab0560..b3b3182b8c96 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/external_service.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/external_service.yaml @@ -1,4 +1,5 @@ type: object +nullable: true properties: connector_id: type: string diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/rule.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/rule.yaml new file mode 100644 index 000000000000..8f18d420ae91 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/rule.yaml @@ -0,0 +1,18 @@ +title: Alerting rule +description: > + The rule that is associated with the alert. It is required only when + `type` is `alert`. This functionality is in technical preview and may be + changed or removed in a future release. Elastic will apply best effort to + fix any issues, but features in technical preview are not subject to the + support SLA of official GA features. +type: object +x-technical-preview: true +properties: + id: + description: The rule identifier. + type: string + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + description: The rule name. + type: string + example: security_rule \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/update_alert_comment_request_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/update_alert_comment_request_properties.yaml index 2d91b007d431..2c7bd5dcc121 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/update_alert_comment_request_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/update_alert_comment_request_properties.yaml @@ -1,20 +1,17 @@ +title: Update case comment request properties for alerts +description: Defines properties for case comment requests when type is alert. +required: + - alertId + - id + - index + - owner + - rule + - type + - version type: object properties: - alertId: - description: > - The alert identifier. It is required only when `type` is `alert`. If it is - an array, `index` must also be an array. This functionality is in - technical preview and may be changed or removed in a future release. - Elastic will apply best effort to fix any issues, but features in - technical preview are not subject to the support SLA of official GA - features. - oneOf: - - type: string - - type: array - items: - type: string - x-technical-preview: true - example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + alertId: + $ref: 'alert_identifiers.yaml' id: type: string description: > @@ -22,31 +19,11 @@ properties: get comments API. example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 index: - description: > - The alert index. It is required only when `type` is `alert`. If it is an - array, `alertId` must also be an array. This functionality is in technical - preview and may be changed or removed in a future release. Elastic will - apply best effort to fix any issues, but features in technical preview are - not subject to the support SLA of official GA features. - oneOf: - - type: string - - type: array - items: - type: string - x-technical-preview: true + $ref: 'alert_indices.yaml' owner: $ref: 'owners.yaml' rule: - description: > - The rule that is associated with the alert. It is required only when - `type` is `alert`. This functionality is in technical preview and may be - changed or removed in a future release. Elastic will apply best effort to - fix any issues, but features in technical preview are not subject to the - support SLA of official GA features. - type: object - x-technical-preview: true - properties: - $ref: 'rule_properties.yaml' + $ref: 'rule.yaml' type: description: The type of comment. type: string @@ -58,12 +35,4 @@ properties: The current comment version. To retrieve version values, use the get comments API. type: string - example: Wzk1LDFd -required: - - alertId - - id - - index - - owner - - rule - - type - - version \ No newline at end of file + example: Wzk1LDFd \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_comment_request.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_comment_request.yaml new file mode 100644 index 000000000000..cc3974c29194 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_comment_request.yaml @@ -0,0 +1,9 @@ +title: Update case comment request +description: >- + The update case comment API request body varies depending on whether you are + updating an alert or a comment. +discriminator: + propertyName: type +oneOf: + - $ref: 'update_alert_comment_request_properties.yaml' + - $ref: 'update_user_comment_request_properties.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/update_user_comment_request_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/update_user_comment_request_properties.yaml index 83d7f0715da2..22fb76d9bba7 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/update_user_comment_request_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/update_user_comment_request_properties.yaml @@ -1,3 +1,5 @@ +title: Update case comment request properties for user comments +description: Defines properties for case comment requests when type is user. type: object properties: comment: diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/user_comment_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/user_comment_response_properties.yaml index 0df26aee0758..b1727d3279ab 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/user_comment_response_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/user_comment_response_properties.yaml @@ -1,4 +1,7 @@ +title: Case response properties for user comments type: object +required: + - type properties: comment: type: string @@ -8,9 +11,7 @@ properties: format: date-time example: 2022-05-13T09:16:17.416Z created_by: - type: object - properties: - $ref: 'user_properties.yaml' + $ref: 'case_response_created_by_properties.yaml' id: type: string example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 @@ -22,25 +23,19 @@ properties: nullable: true example: null pushed_by: - type: object - properties: - $ref: 'user_properties.yaml' - nullable: true - example: null + $ref: 'case_response_pushed_by_properties.yaml' type: type: string example: user + enum: + - user updated_at: type: string format: date-time nullable: true example: null updated_by: - type: object - properties: - $ref: 'user_properties.yaml' - nullable: true - example: null + $ref: 'case_response_updated_by_properties.yaml' version: type: string example: WzIwNDMxLDFd \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/user_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/user_properties.yaml index 1c3596dc6f9b..19b76a6000c0 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/user_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/user_properties.yaml @@ -1,12 +1,15 @@ email: type: string example: null + nullable: true full_name: type: string example: null + nullable: true username: type: string example: elastic + nullable: true profile_uid: type: string example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0 diff --git a/x-pack/plugins/cases/docs/openapi/entrypoint-min.yaml b/x-pack/plugins/cases/docs/openapi/entrypoint-min.yaml new file mode 100644 index 000000000000..595d33a7465d --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/entrypoint-min.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.1 +info: + title: Cases + description: OpenAPI schema for Cases endpoints + version: '0.2' + contact: + name: Cases Team + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +tags: + - name: cases + description: Case APIs enable you to open and track issues. +servers: + - url: 'http://localhost:5601' + description: local +paths: +# '/s/{spaceId}/api/cases': +# $ref: 'paths/s@{spaceid}@api@cases.yaml' +# '/s/{spaceId}/api/cases/_find': +# $ref: 'paths/s@{spaceid}@api@cases@_find.yaml' +# '/s/{spaceId}/api/cases/alerts/{alertId}': +# $ref: 'paths/s@{spaceid}@api@cases@alerts@{alertid}.yaml' +# '/s/{spaceId}/api/cases/configure': +# $ref: paths/s@{spaceid}@api@cases@configure.yaml +# '/s/{spaceId}/api/cases/configure/{configurationId}': +# $ref: paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml +# '/s/{spaceId}/api/cases/configure/connectors/_find': +# $ref: paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml +# '/s/{spaceId}/api/cases/reporters': +# $ref: 'paths/s@{spaceid}@api@cases@reporters.yaml' +# '/s/{spaceId}/api/cases/status': +# $ref: 'paths/s@{spaceid}@api@cases@status.yaml' +# '/s/{spaceId}/api/cases/tags': +# $ref: 'paths/s@{spaceid}@api@cases@tags.yaml' +# '/s/{spaceId}/api/cases/{caseId}': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}.yaml' +# '/s/{spaceId}/api/cases/{caseId}/alerts': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@alerts.yaml' + '/s/{spaceId}/api/cases/{caseId}/comments': + $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments.yaml' +# '/s/{spaceId}/api/cases/{caseId}/comments/{commentId}': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml' +# '/s/{spaceId}/api/cases/{caseId}/connector/{connectorId}/_push': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml' +# '/s/{spaceId}/api/cases/{caseId}/user_actions': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml' +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + apiKeyAuth: + type: apiKey + in: header + name: ApiKey +security: + - basicAuth: [] + - apiKeyAuth: [] diff --git a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml index 6995d1482e0a..6f467f77ef93 100644 --- a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml +++ b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml @@ -11,8 +11,6 @@ info: tags: - name: cases description: Case APIs enable you to open and track issues. - - name: kibana - description: Kibana APIs enable you to interact with Kibana features. servers: - url: 'http://localhost:5601' description: local diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml index 59abb5853182..80092f32cbe6 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml @@ -57,11 +57,9 @@ post: '200': description: Indicates a successful call. content: - application/json; charset=utf-8: + application/json: schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' + $ref: '../components/schemas/case_response_properties.yaml' examples: createCaseResponse: $ref: '../components/examples/create_case_response.yaml' @@ -160,11 +158,9 @@ patch: '200': description: Indicates a successful call. content: - application/json; charset=utf-8: + application/json: schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' + $ref: '../components/schemas/case_response_properties.yaml' examples: updateCaseResponse: $ref: '../components/examples/update_case_response.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml index a26032124835..6d5bb0c2812c 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml @@ -138,9 +138,7 @@ get: cases: type: array items: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' + $ref: '../components/schemas/case_response_properties.yaml' count_closed_cases: type: integer count_in_progress_cases: diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml index 9e8ca4660c44..d32d24b9fa01 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml @@ -21,11 +21,9 @@ get: '200': description: Indicates a successful call. content: - application/json; charset=utf-8: + application/json: schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' + $ref: '../components/schemas/case_response_properties.yaml' examples: getCaseResponse: $ref: '../components/examples/get_case_response.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml index c8a045267a49..2246ff1d16a9 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml @@ -12,12 +12,11 @@ post: - $ref: '../components/parameters/case_id.yaml' - $ref: '../components/parameters/space_id.yaml' requestBody: + required: true content: application/json: schema: - oneOf: - - $ref: '../components/schemas/add_alert_comment_request_properties.yaml' - - $ref: '../components/schemas/add_user_comment_request_properties.yaml' + $ref: '../components/schemas/add_case_comment_request.yaml' examples: createCaseCommentRequest: $ref: '../components/examples/add_comment_request.yaml' @@ -25,11 +24,9 @@ post: '200': description: Indicates a successful call. content: - application/json; charset=utf-8: + application/json: schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' + $ref: '../components/schemas/case_response_properties.yaml' examples: createCaseCommentResponse: $ref: '../components/examples/add_comment_response.yaml' @@ -70,12 +67,11 @@ patch: - $ref: '../components/parameters/case_id.yaml' - $ref: '../components/parameters/space_id.yaml' requestBody: + required: true content: application/json: schema: - oneOf: - - $ref: '../components/schemas/update_alert_comment_request_properties.yaml' - - $ref: '../components/schemas/update_user_comment_request_properties.yaml' + $ref: '../components/schemas/update_case_comment_request.yaml' examples: updateCaseCommentRequest: $ref: '../components/examples/update_comment_request.yaml' @@ -83,11 +79,9 @@ patch: '200': description: Indicates a successful call. content: - application/json; charset=utf-8: + application/json: schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' + $ref: '../components/schemas/case_response_properties.yaml' examples: updateCaseCommentResponse: $ref: '../components/examples/update_comment_response.yaml' @@ -111,14 +105,10 @@ get: '200': description: Indicates a successful call. content: - application/json; charset=utf-8: + application/json: schema: - type: array - items: - anyOf: - - $ref: '../components/schemas/alert_comment_response_properties.yaml' - - $ref: '../components/schemas/user_comment_response_properties.yaml' - examples: {} + $ref: '../components/schemas/case_response_properties.yaml' + servers: - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml index 32caad2bc408..37587abd760e 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml @@ -21,11 +21,9 @@ post: '200': description: Indicates a successful call. content: - application/json; charset=utf-8: + application/json: schema: - type: object - properties: - $ref: '../components/schemas/case_response_properties.yaml' + $ref: '../components/schemas/case_response_properties.yaml' examples: pushCaseResponse: $ref: '../components/examples/push_case_response.yaml' diff --git a/x-pack/plugins/cases/public/common/mock/permissions.ts b/x-pack/plugins/cases/public/common/mock/permissions.ts index 1166dbed8ca8..7395e966037e 100644 --- a/x-pack/plugins/cases/public/common/mock/permissions.ts +++ b/x-pack/plugins/cases/public/common/mock/permissions.ts @@ -17,6 +17,8 @@ export const noUpdateCasesPermissions = () => buildCasesPermissions({ update: fa export const noPushCasesPermissions = () => buildCasesPermissions({ push: false }); export const noDeleteCasesPermissions = () => buildCasesPermissions({ delete: false }); export const writeCasesPermissions = () => buildCasesPermissions({ read: false }); +export const onlyDeleteCasesPermission = () => + buildCasesPermissions({ read: false, create: false, update: false, delete: true, push: false }); export const buildCasesPermissions = (overrides: Partial> = {}) => { const create = overrides.create ?? true; diff --git a/x-pack/plugins/cases/public/components/actions/delete/translations.ts b/x-pack/plugins/cases/public/components/actions/delete/translations.ts new file mode 100644 index 000000000000..f875ea2c000c --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/delete/translations.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +export { DELETED_CASES } from '../../../common/translations'; + +export const BULK_ACTION_DELETE_LABEL = i18n.translate('xpack.cases.actions.deleteMultipleCases', { + defaultMessage: 'Delete cases', +}); + +export const DELETE_ACTION_LABEL = i18n.translate('xpack.cases.actions.deleteSingleCase', { + defaultMessage: 'Delete case', +}); diff --git a/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.test.tsx b/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.test.tsx new file mode 100644 index 000000000000..747aa0e84e1d --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.test.tsx @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AppMockRenderer, createAppMockRenderer } from '../../../common/mock'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { useDeleteAction } from './use_delete_action'; + +import * as api from '../../../containers/api'; +import { basicCase } from '../../../containers/mock'; + +jest.mock('../../../containers/api'); + +describe('useDeleteAction', () => { + let appMockRender: AppMockRenderer; + const onAction = jest.fn(); + const onActionSuccess = jest.fn(); + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders an action with one case', async () => { + const { result } = renderHook( + () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current.getAction([basicCase])).toMatchInlineSnapshot(` + Object { + "data-test-subj": "cases-bulk-action-delete", + "disabled": false, + "icon": , + "key": "cases-bulk-action-delete", + "name": + Delete case + , + "onClick": [Function], + } + `); + }); + + it('renders an action with multiple cases', async () => { + const { result } = renderHook( + () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current.getAction([basicCase, basicCase])).toMatchInlineSnapshot(` + Object { + "data-test-subj": "cases-bulk-action-delete", + "disabled": false, + "icon": , + "key": "cases-bulk-action-delete", + "name": + Delete cases + , + "onClick": [Function], + } + `); + }); + + it('deletes the selected cases', async () => { + const deleteSpy = jest.spyOn(api, 'deleteCases'); + + const { result, waitFor } = renderHook( + () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const action = result.current.getAction([basicCase]); + + act(() => { + action.onClick(); + }); + + expect(onAction).toHaveBeenCalled(); + expect(result.current.isModalVisible).toBe(true); + + act(() => { + result.current.onConfirmDeletion(); + }); + + await waitFor(() => { + expect(result.current.isModalVisible).toBe(false); + expect(onActionSuccess).toHaveBeenCalled(); + expect(deleteSpy).toHaveBeenCalledWith(['basic-case-id'], expect.anything()); + }); + }); + + it('closes the modal', async () => { + const { result, waitFor } = renderHook( + () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const action = result.current.getAction([basicCase]); + + act(() => { + action.onClick(); + }); + + expect(result.current.isModalVisible).toBe(true); + + act(() => { + result.current.onCloseModal(); + }); + + await waitFor(() => { + expect(result.current.isModalVisible).toBe(false); + }); + }); + + it('shows the success toaster correctly when delete one case', async () => { + const { result, waitFor } = renderHook( + () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const action = result.current.getAction([basicCase]); + + act(() => { + action.onClick(); + }); + + act(() => { + result.current.onConfirmDeletion(); + }); + + await waitFor(() => { + expect(appMockRender.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith( + 'Deleted case' + ); + }); + }); + + it('shows the success toaster correctly when delete multiple case', async () => { + const { result, waitFor } = renderHook( + () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const action = result.current.getAction([basicCase, basicCase]); + + act(() => { + action.onClick(); + }); + + act(() => { + result.current.onConfirmDeletion(); + }); + + await waitFor(() => { + expect(appMockRender.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith( + 'Deleted 2 cases' + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.tsx b/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.tsx new file mode 100644 index 000000000000..92184dbd6464 --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useState } from 'react'; +import { EuiIcon, EuiTextColor, useEuiTheme } from '@elastic/eui'; +import { Case } from '../../../../common'; +import { useDeleteCases } from '../../../containers/use_delete_cases'; + +import * as i18n from './translations'; +import { UseActionProps } from '../types'; +import { useCasesContext } from '../../cases_context/use_cases_context'; + +const getDeleteActionTitle = (totalCases: number): string => + totalCases > 1 ? i18n.BULK_ACTION_DELETE_LABEL : i18n.DELETE_ACTION_LABEL; + +export const useDeleteAction = ({ onAction, onActionSuccess, isDisabled }: UseActionProps) => { + const euiTheme = useEuiTheme(); + const { permissions } = useCasesContext(); + const [isModalVisible, setIsModalVisible] = useState(false); + const [caseToBeDeleted, setCaseToBeDeleted] = useState([]); + const canDelete = permissions.delete; + const isActionDisabled = isDisabled || !canDelete; + + const onCloseModal = useCallback(() => setIsModalVisible(false), []); + const openModal = useCallback( + (selectedCases: Case[]) => { + onAction(); + setIsModalVisible(true); + setCaseToBeDeleted(selectedCases); + }, + [onAction] + ); + + const { mutate: deleteCases } = useDeleteCases(); + + const onConfirmDeletion = useCallback(() => { + onCloseModal(); + deleteCases( + { + caseIds: caseToBeDeleted.map(({ id }) => id), + successToasterTitle: i18n.DELETED_CASES(caseToBeDeleted.length), + }, + { onSuccess: onActionSuccess } + ); + }, [deleteCases, onActionSuccess, onCloseModal, caseToBeDeleted]); + + const color = isActionDisabled ? euiTheme.euiTheme.colors.disabled : 'danger'; + + const getAction = (selectedCases: Case[]) => { + return { + name: {getDeleteActionTitle(selectedCases.length)}, + onClick: () => openModal(selectedCases), + disabled: isActionDisabled, + 'data-test-subj': 'cases-bulk-action-delete', + icon: , + key: 'cases-bulk-action-delete', + }; + }; + + return { getAction, isModalVisible, onConfirmDeletion, onCloseModal, canDelete }; +}; + +export type UseDeleteAction = ReturnType; diff --git a/x-pack/plugins/cases/public/components/actions/severity/translations.ts b/x-pack/plugins/cases/public/components/actions/severity/translations.ts new file mode 100644 index 000000000000..81e0caf848d1 --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/severity/translations.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { CaseSeverity } from '../../../../common/api'; +import { severities } from '../../severity/config'; + +const SET_SEVERITY = ({ + totalCases, + severity, + caseTitle, +}: { + totalCases: number; + severity: string; + caseTitle?: string; +}) => + i18n.translate('xpack.cases.actions.severity', { + values: { caseTitle, totalCases, severity }, + defaultMessage: + '{totalCases, plural, =1 {Case "{caseTitle}" was} other {{totalCases} cases were}} set to {severity}', + }); + +export const SET_SEVERITY_LOW = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + SET_SEVERITY({ + totalCases, + caseTitle, + severity: severities[CaseSeverity.LOW].label, + }); + +export const SET_SEVERITY_MEDIUM = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + SET_SEVERITY({ + totalCases, + caseTitle, + severity: severities[CaseSeverity.MEDIUM].label, + }); + +export const SET_SEVERITY_HIGH = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + SET_SEVERITY({ + totalCases, + caseTitle, + severity: severities[CaseSeverity.HIGH].label, + }); + +export const SET_SEVERITY_CRITICAL = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + SET_SEVERITY({ + totalCases, + caseTitle, + severity: severities[CaseSeverity.CRITICAL].label, + }); diff --git a/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.test.tsx b/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.test.tsx new file mode 100644 index 000000000000..9705d5ffeb2b --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.test.tsx @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AppMockRenderer, createAppMockRenderer } from '../../../common/mock'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { useSeverityAction } from './use_severity_action'; + +import * as api from '../../../containers/api'; +import { basicCase } from '../../../containers/mock'; +import { CaseSeverity } from '../../../../common/api'; + +jest.mock('../../../containers/api'); + +describe('useSeverityAction', () => { + let appMockRender: AppMockRenderer; + const onAction = jest.fn(); + const onActionSuccess = jest.fn(); + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders an action', async () => { + const { result } = renderHook( + () => + useSeverityAction({ + onAction, + onActionSuccess, + isDisabled: false, + }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current.getActions([basicCase])).toMatchInlineSnapshot(` + Array [ + Object { + "data-test-subj": "cases-bulk-action-severity-low", + "disabled": true, + "icon": "empty", + "key": "cases-bulk-action-severity-low", + "name": "Low", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-severity-medium", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-action-severity-medium", + "name": "Medium", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-severity-high", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-action-severity-high", + "name": "High", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-severity-critical", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-action-severity-critical", + "name": "Critical", + "onClick": [Function], + }, + ] + `); + }); + + it('update the severity cases', async () => { + const updateSpy = jest.spyOn(api, 'updateCases'); + + const { result, waitFor } = renderHook( + () => useSeverityAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([basicCase]); + + for (const [index, severity] of [ + CaseSeverity.LOW, + CaseSeverity.MEDIUM, + CaseSeverity.HIGH, + CaseSeverity.CRITICAL, + ].entries()) { + act(() => { + // @ts-expect-error: onClick expects a MouseEvent argument + actions[index]!.onClick(); + }); + + await waitFor(() => { + expect(onAction).toHaveBeenCalled(); + expect(onActionSuccess).toHaveBeenCalled(); + expect(updateSpy).toHaveBeenCalledWith( + [{ severity, id: basicCase.id, version: basicCase.version }], + expect.anything() + ); + }); + } + }); + + const singleCaseTests = [ + [CaseSeverity.LOW, 0, 'Case "Another horrible breach!!" was set to Low'], + [CaseSeverity.MEDIUM, 1, 'Case "Another horrible breach!!" was set to Medium'], + [CaseSeverity.HIGH, 2, 'Case "Another horrible breach!!" was set to High'], + [CaseSeverity.CRITICAL, 3, 'Case "Another horrible breach!!" was set to Critical'], + ]; + + it.each(singleCaseTests)( + 'shows the success toaster correctly when updating the severity of the case: %s', + async (_, index, expectedMessage) => { + const { result, waitFor } = renderHook( + () => useSeverityAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([basicCase]); + + act(() => { + // @ts-expect-error: onClick expects a MouseEvent argument + actions[index]!.onClick(); + }); + + await waitFor(() => { + expect(appMockRender.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith( + expectedMessage + ); + }); + } + ); + + const multipleCasesTests: Array<[CaseSeverity, number, string]> = [ + [CaseSeverity.LOW, 0, '2 cases were set to Low'], + [CaseSeverity.MEDIUM, 1, '2 cases were set to Medium'], + [CaseSeverity.HIGH, 2, '2 cases were set to High'], + [CaseSeverity.CRITICAL, 3, '2 cases were set to Critical'], + ]; + + it.each(multipleCasesTests)( + 'shows the success toaster correctly when updating the severity of the case: %s', + async (_, index, expectedMessage) => { + const { result, waitFor } = renderHook( + () => useSeverityAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([basicCase, basicCase]); + + act(() => { + // @ts-expect-error: onClick expects a MouseEvent argument + actions[index]!.onClick(); + }); + + await waitFor(() => { + expect(appMockRender.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith( + expectedMessage + ); + }); + } + ); + + const disabledTests: Array<[CaseSeverity, number]> = [ + [CaseSeverity.LOW, 0], + [CaseSeverity.MEDIUM, 1], + [CaseSeverity.HIGH, 2], + [CaseSeverity.CRITICAL, 3], + ]; + + it.each(disabledTests)('disables the severity button correctly: %s', async (severity, index) => { + const { result } = renderHook( + () => useSeverityAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([{ ...basicCase, severity }]); + expect(actions[index].disabled).toBe(true); + }); + + it.each(disabledTests)( + 'disables the severity button correctly if isDisabled=true: %s', + async (severity, index) => { + const { result } = renderHook( + () => useSeverityAction({ onAction, onActionSuccess, isDisabled: true }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([basicCase]); + expect(actions[index].disabled).toBe(true); + } + ); +}); diff --git a/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.tsx b/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.tsx new file mode 100644 index 000000000000..a4651cee8261 --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback } from 'react'; +import { EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; +import { CaseSeverity } from '../../../../common/api'; +import { useUpdateCases } from '../../../containers/use_bulk_update_case'; +import { Case } from '../../../../common'; + +import * as i18n from './translations'; +import { UseActionProps } from '../types'; +import { useCasesContext } from '../../cases_context/use_cases_context'; +import { severities } from '../../severity/config'; + +const getSeverityToasterMessage = (severity: CaseSeverity, cases: Case[]): string => { + const totalCases = cases.length; + const caseTitle = totalCases === 1 ? cases[0].title : ''; + + switch (severity) { + case CaseSeverity.LOW: + return i18n.SET_SEVERITY_LOW({ totalCases, caseTitle }); + case CaseSeverity.MEDIUM: + return i18n.SET_SEVERITY_MEDIUM({ totalCases, caseTitle }); + case CaseSeverity.HIGH: + return i18n.SET_SEVERITY_HIGH({ totalCases, caseTitle }); + case CaseSeverity.CRITICAL: + return i18n.SET_SEVERITY_CRITICAL({ totalCases, caseTitle }); + + default: + return ''; + } +}; + +interface UseSeverityActionProps extends UseActionProps { + selectedSeverity?: CaseSeverity; +} + +const shouldDisableSeverity = (cases: Case[], severity: CaseSeverity) => + cases.every((theCase) => theCase.severity === severity); + +export const useSeverityAction = ({ + onAction, + onActionSuccess, + isDisabled, + selectedSeverity, +}: UseSeverityActionProps) => { + const { mutate: updateCases } = useUpdateCases(); + const { permissions } = useCasesContext(); + const canUpdateSeverity = permissions.update; + const isActionDisabled = isDisabled || !canUpdateSeverity; + + const handleUpdateCaseSeverity = useCallback( + (selectedCases: Case[], severity: CaseSeverity) => { + onAction(); + const casesToUpdate = selectedCases.map((theCase) => ({ + severity, + id: theCase.id, + version: theCase.version, + })); + + updateCases( + { + cases: casesToUpdate, + successToasterTitle: getSeverityToasterMessage(severity, selectedCases), + }, + { onSuccess: onActionSuccess } + ); + }, + [onAction, updateCases, onActionSuccess] + ); + + const getSeverityIcon = (severity: CaseSeverity): string => + selectedSeverity && selectedSeverity === severity ? 'check' : 'empty'; + + const getActions = (selectedCases: Case[]): EuiContextMenuPanelItemDescriptor[] => { + return [ + { + name: severities[CaseSeverity.LOW].label, + icon: getSeverityIcon(CaseSeverity.LOW), + onClick: () => handleUpdateCaseSeverity(selectedCases, CaseSeverity.LOW), + disabled: isActionDisabled || shouldDisableSeverity(selectedCases, CaseSeverity.LOW), + 'data-test-subj': 'cases-bulk-action-severity-low', + key: 'cases-bulk-action-severity-low', + }, + { + name: severities[CaseSeverity.MEDIUM].label, + icon: getSeverityIcon(CaseSeverity.MEDIUM), + onClick: () => handleUpdateCaseSeverity(selectedCases, CaseSeverity.MEDIUM), + disabled: isActionDisabled || shouldDisableSeverity(selectedCases, CaseSeverity.MEDIUM), + 'data-test-subj': 'cases-bulk-action-severity-medium', + key: 'cases-bulk-action-severity-medium', + }, + { + name: severities[CaseSeverity.HIGH].label, + icon: getSeverityIcon(CaseSeverity.HIGH), + onClick: () => handleUpdateCaseSeverity(selectedCases, CaseSeverity.HIGH), + disabled: isActionDisabled || shouldDisableSeverity(selectedCases, CaseSeverity.HIGH), + 'data-test-subj': 'cases-bulk-action-severity-high', + key: 'cases-bulk-action-severity-high', + }, + { + name: severities[CaseSeverity.CRITICAL].label, + icon: getSeverityIcon(CaseSeverity.CRITICAL), + onClick: () => handleUpdateCaseSeverity(selectedCases, CaseSeverity.CRITICAL), + disabled: isActionDisabled || shouldDisableSeverity(selectedCases, CaseSeverity.CRITICAL), + 'data-test-subj': 'cases-bulk-action-severity-critical', + key: 'cases-bulk-action-severity-critical', + }, + ]; + }; + + return { getActions, canUpdateSeverity }; +}; + +export type UseSeverityAction = ReturnType; diff --git a/x-pack/plugins/cases/public/components/actions/status/translations.ts b/x-pack/plugins/cases/public/components/actions/status/translations.ts new file mode 100644 index 000000000000..67fa3544906d --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/status/translations.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +export { MARK_CASE_IN_PROGRESS, OPEN_CASE, CLOSE_CASE } from '../../../common/translations'; + +export const CLOSED_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.cases.actions.closedCases', { + values: { caseTitle, totalCases }, + defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const REOPENED_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.cases.actions.reopenedCases', { + values: { caseTitle, totalCases }, + defaultMessage: 'Opened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const MARK_IN_PROGRESS_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.cases.actions.markInProgressCases', { + values: { caseTitle, totalCases }, + defaultMessage: + 'Marked {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}} as in progress', + }); + +export const BULK_ACTION_STATUS_CLOSE = i18n.translate('xpack.cases.actions.status.close', { + defaultMessage: 'Close selected', +}); + +export const BULK_ACTION_STATUS_OPEN = i18n.translate('xpack.cases.actions.status.open', { + defaultMessage: 'Open selected', +}); + +export const BULK_ACTION_STATUS_IN_PROGRESS = i18n.translate( + 'xpack.cases.actions.status.inProgress', + { + defaultMessage: 'Mark in progress', + } +); diff --git a/x-pack/plugins/cases/public/components/actions/status/use_status_action.test.tsx b/x-pack/plugins/cases/public/components/actions/status/use_status_action.test.tsx new file mode 100644 index 000000000000..a0f16dbaa176 --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/status/use_status_action.test.tsx @@ -0,0 +1,198 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AppMockRenderer, createAppMockRenderer } from '../../../common/mock'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { useStatusAction } from './use_status_action'; + +import * as api from '../../../containers/api'; +import { basicCase } from '../../../containers/mock'; +import { CaseStatuses } from '../../../../common'; + +jest.mock('../../../containers/api'); + +describe('useStatusAction', () => { + let appMockRender: AppMockRenderer; + const onAction = jest.fn(); + const onActionSuccess = jest.fn(); + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders an action', async () => { + const { result } = renderHook( + () => + useStatusAction({ + onAction, + onActionSuccess, + isDisabled: false, + }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current.getActions([basicCase])).toMatchInlineSnapshot(` + Array [ + Object { + "data-test-subj": "cases-bulk-action-status-open", + "disabled": true, + "icon": "empty", + "key": "cases-bulk-action-status-open", + "name": "Open", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-status-in-progress", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-action-status-in-progress", + "name": "In progress", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-status-closed", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-status-action", + "name": "Closed", + "onClick": [Function], + }, + ] + `); + }); + + it('update the status cases', async () => { + const updateSpy = jest.spyOn(api, 'updateCases'); + + const { result, waitFor } = renderHook( + () => useStatusAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([basicCase]); + + for (const [index, status] of [ + CaseStatuses.open, + CaseStatuses['in-progress'], + CaseStatuses.closed, + ].entries()) { + act(() => { + // @ts-expect-error: onClick expects a MouseEvent argument + actions[index]!.onClick(); + }); + + await waitFor(() => { + expect(onAction).toHaveBeenCalled(); + expect(onActionSuccess).toHaveBeenCalled(); + expect(updateSpy).toHaveBeenCalledWith( + [{ status, id: basicCase.id, version: basicCase.version }], + expect.anything() + ); + }); + } + }); + + const singleCaseTests = [ + [CaseStatuses.open, 0, 'Opened "Another horrible breach!!"'], + [CaseStatuses['in-progress'], 1, 'Marked "Another horrible breach!!" as in progress'], + [CaseStatuses.closed, 2, 'Closed "Another horrible breach!!"'], + ]; + + it.each(singleCaseTests)( + 'shows the success toaster correctly when updating the status of the case: %s', + async (_, index, expectedMessage) => { + const { result, waitFor } = renderHook( + () => useStatusAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([basicCase]); + + act(() => { + // @ts-expect-error: onClick expects a MouseEvent argument + actions[index]!.onClick(); + }); + + await waitFor(() => { + expect(appMockRender.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith( + expectedMessage + ); + }); + } + ); + + const multipleCasesTests: Array<[CaseStatuses, number, string]> = [ + [CaseStatuses.open, 0, 'Opened 2 cases'], + [CaseStatuses['in-progress'], 1, 'Marked 2 cases as in progress'], + [CaseStatuses.closed, 2, 'Closed 2 cases'], + ]; + + it.each(multipleCasesTests)( + 'shows the success toaster correctly when updating the status of the case: %s', + async (_, index, expectedMessage) => { + const { result, waitFor } = renderHook( + () => useStatusAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([basicCase, basicCase]); + + act(() => { + // @ts-expect-error: onClick expects a MouseEvent argument + actions[index]!.onClick(); + }); + + await waitFor(() => { + expect(appMockRender.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith( + expectedMessage + ); + }); + } + ); + + const disabledTests: Array<[CaseStatuses, number]> = [ + [CaseStatuses.open, 0], + [CaseStatuses['in-progress'], 1], + [CaseStatuses.closed, 2], + ]; + + it.each(disabledTests)('disables the status button correctly: %s', async (status, index) => { + const { result } = renderHook( + () => useStatusAction({ onAction, onActionSuccess, isDisabled: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([{ ...basicCase, status }]); + expect(actions[index].disabled).toBe(true); + }); + + it.each(disabledTests)( + 'disables the status button correctly if isDisabled=true: %s', + async (status, index) => { + const { result } = renderHook( + () => useStatusAction({ onAction, onActionSuccess, isDisabled: true }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const actions = result.current.getActions([basicCase]); + expect(actions[index].disabled).toBe(true); + } + ); +}); diff --git a/x-pack/plugins/cases/public/components/actions/status/use_status_action.tsx b/x-pack/plugins/cases/public/components/actions/status/use_status_action.tsx new file mode 100644 index 000000000000..8f1100aaab90 --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/status/use_status_action.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback } from 'react'; +import { EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; +import { useUpdateCases } from '../../../containers/use_bulk_update_case'; +import { Case, CaseStatuses } from '../../../../common'; + +import * as i18n from './translations'; +import { UseActionProps } from '../types'; +import { statuses } from '../../status'; +import { useCasesContext } from '../../cases_context/use_cases_context'; + +const getStatusToasterMessage = (status: CaseStatuses, cases: Case[]): string => { + const totalCases = cases.length; + const caseTitle = totalCases === 1 ? cases[0].title : ''; + + if (status === CaseStatuses.open) { + return i18n.REOPENED_CASES({ totalCases, caseTitle }); + } else if (status === CaseStatuses['in-progress']) { + return i18n.MARK_IN_PROGRESS_CASES({ totalCases, caseTitle }); + } else if (status === CaseStatuses.closed) { + return i18n.CLOSED_CASES({ totalCases, caseTitle }); + } + + return ''; +}; + +interface UseStatusActionProps extends UseActionProps { + selectedStatus?: CaseStatuses; +} + +const shouldDisableStatus = (cases: Case[], status: CaseStatuses) => + cases.every((theCase) => theCase.status === status); + +export const useStatusAction = ({ + onAction, + onActionSuccess, + isDisabled, + selectedStatus, +}: UseStatusActionProps) => { + const { mutate: updateCases } = useUpdateCases(); + const { permissions } = useCasesContext(); + const canUpdateStatus = permissions.update; + const isActionDisabled = isDisabled || !canUpdateStatus; + + const handleUpdateCaseStatus = useCallback( + (selectedCases: Case[], status: CaseStatuses) => { + onAction(); + const casesToUpdate = selectedCases.map((theCase) => ({ + status, + id: theCase.id, + version: theCase.version, + })); + + updateCases( + { + cases: casesToUpdate, + successToasterTitle: getStatusToasterMessage(status, selectedCases), + }, + { onSuccess: onActionSuccess } + ); + }, + [onAction, updateCases, onActionSuccess] + ); + + const getStatusIcon = (status: CaseStatuses): string => + selectedStatus && selectedStatus === status ? 'check' : 'empty'; + + const getActions = (selectedCases: Case[]): EuiContextMenuPanelItemDescriptor[] => { + return [ + { + name: statuses[CaseStatuses.open].label, + icon: getStatusIcon(CaseStatuses.open), + onClick: () => handleUpdateCaseStatus(selectedCases, CaseStatuses.open), + disabled: isActionDisabled || shouldDisableStatus(selectedCases, CaseStatuses.open), + 'data-test-subj': 'cases-bulk-action-status-open', + key: 'cases-bulk-action-status-open', + }, + { + name: statuses[CaseStatuses['in-progress']].label, + icon: getStatusIcon(CaseStatuses['in-progress']), + onClick: () => handleUpdateCaseStatus(selectedCases, CaseStatuses['in-progress']), + disabled: + isActionDisabled || shouldDisableStatus(selectedCases, CaseStatuses['in-progress']), + 'data-test-subj': 'cases-bulk-action-status-in-progress', + key: 'cases-bulk-action-status-in-progress', + }, + { + name: statuses[CaseStatuses.closed].label, + icon: getStatusIcon(CaseStatuses.closed), + onClick: () => handleUpdateCaseStatus(selectedCases, CaseStatuses.closed), + disabled: isActionDisabled || shouldDisableStatus(selectedCases, CaseStatuses.closed), + 'data-test-subj': 'cases-bulk-action-status-closed', + key: 'cases-bulk-status-action', + }, + ]; + }; + + return { getActions, canUpdateStatus }; +}; + +export type UseStatusAction = ReturnType; diff --git a/x-pack/plugins/cases/public/components/actions/types.ts b/x-pack/plugins/cases/public/components/actions/types.ts new file mode 100644 index 000000000000..20a566a20c67 --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface UseActionProps { + onAction: () => void; + onActionSuccess: () => void; + isDisabled: boolean; +} diff --git a/x-pack/plugins/cases/public/components/all_cases/actions.tsx b/x-pack/plugins/cases/public/components/all_cases/actions.tsx deleted file mode 100644 index f978cd1b3f20..000000000000 --- a/x-pack/plugins/cases/public/components/all_cases/actions.tsx +++ /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 { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; - -import { Case } from '../../containers/types'; -import * as i18n from './translations'; - -interface GetActions { - deleteCaseOnClick: (deleteCase: Case) => void; -} - -export const getActions = ({ - deleteCaseOnClick, -}: GetActions): Array> => [ - { - description: i18n.DELETE_CASE(), - icon: 'trash', - color: 'danger', - name: i18n.DELETE_CASE(), - onClick: deleteCaseOnClick, - type: 'icon', - 'data-test-subj': 'action-delete', - }, -]; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index 1cc3a5f0263e..63ef59d695d8 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -18,6 +18,7 @@ import { AppMockRenderer, createAppMockRenderer, noDeleteCasesPermissions, + readCasesPermissions, TestProviders, } from '../../common/mock'; import { useGetCasesMockState, connectorsMock } from '../../containers/mock'; @@ -28,7 +29,7 @@ import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { getEmptyTagValue } from '../empty_value'; import { useKibana } from '../../common/lib/kibana'; import { AllCasesList } from './all_cases_list'; -import { CasesColumns, GetCasesColumn, useCasesColumns } from './columns'; +import { GetCasesColumn, useCasesColumns, UseCasesColumnsReturnValue } from './use_cases_columns'; import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; import { registerConnectorsToMockActionRegistry } from '../../common/mock/register_connectors'; import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock'; @@ -97,10 +98,6 @@ describe('AllCasesListGeneric', () => { }; const defaultColumnArgs = { - caseDetailsNavigation: { - href: jest.fn(), - onClick: jest.fn(), - }, filterStatus: CaseStatuses.open, handleIsLoading: jest.fn(), isLoadingCases: [], @@ -161,10 +158,6 @@ describe('AllCasesListGeneric', () => { .prop('value') ).toBe(useGetCasesMockState.data.cases[0].createdAt); - expect( - wrapper.find(`[data-test-subj="case-table-column-severity"]`).first().text().toLowerCase() - ).toBe(useGetCasesMockState.data.cases[0].severity); - expect(wrapper.find(`[data-test-subj="case-table-case-count"]`).first().text()).toEqual( 'Showing 10 cases' ); @@ -236,7 +229,7 @@ describe('AllCasesListGeneric', () => { expect(column.find('span').text()).toEqual(emptyTag); }; - const { result } = renderHook( + const { result } = renderHook( () => useCasesColumns(defaultColumnArgs), { wrapper: ({ children }) => {children}, @@ -244,26 +237,12 @@ describe('AllCasesListGeneric', () => { ); await waitFor(() => { - result.current.map( - (i, key) => - i.name != null && - !Object.prototype.hasOwnProperty.call(i, 'actions') && - checkIt(`${i.name}`, key) + result.current.columns.map( + (i, key) => i.name != null && i.name !== 'Actions' && checkIt(`${i.name}`, key) ); }); }); - it('should render delete actions for case', async () => { - const wrapper = mount( - - - - ); - await waitFor(() => { - expect(wrapper.find('[data-test-subj="action-delete"]').first().props().disabled).toBeFalsy(); - }); - }); - it('should tableHeaderSortButton AllCasesList', async () => { const wrapper = mount( @@ -285,163 +264,25 @@ describe('AllCasesListGeneric', () => { }); }); - it('Updates status when status context menu is updated', async () => { - const wrapper = mount( - - - - ); - wrapper.find(`[data-test-subj="case-view-status-dropdown"] button`).first().simulate('click'); - wrapper - .find(`[data-test-subj="case-view-status-dropdown-closed"] button`) - .first() - .simulate('click'); + it('renders the status column', async () => { + const res = appMockRenderer.render(); - await waitFor(() => { - const firstCase = useGetCasesMockState.data.cases[0]; - expect(updateCaseProperty).toHaveBeenCalledWith({ - caseData: firstCase, - updateKey: 'status', - updateValue: CaseStatuses.closed, - onSuccess: expect.anything(), - }); - }); + expect(res.getByTestId('tableHeaderCell_Status_6')).toBeInTheDocument(); }); - it('should render the case stats', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="cases-count-stats"]')).toBeTruthy(); - }); - - it('Bulk delete', async () => { - const deleteCasesSpy = jest.spyOn(api, 'deleteCases'); - const result = appMockRenderer.render(); - - act(() => { - userEvent.click(result.getByTestId('checkboxSelectAll')); - }); - - act(() => { - userEvent.click(result.getByText('Bulk actions')); - }); - - await waitForEuiPopoverOpen(); - - act(() => { - userEvent.click(result.getByTestId('cases-bulk-delete-button'), undefined, { - skipPointerEventsCheck: true, - }); - }); - - await waitFor(() => { - expect(result.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); - }); - - act(() => { - userEvent.click(result.getByTestId('confirmModalConfirmButton')); - }); + it('renders the severity column', async () => { + const res = appMockRenderer.render(); - await waitFor(() => { - expect(deleteCasesSpy).toHaveBeenCalledWith( - [ - 'basic-case-id', - '1', - '2', - '3', - '4', - 'case-with-alerts-id', - 'case-with-alerts-syncoff-id', - 'case-with-registered-attachment', - ], - expect.anything() - ); - }); + expect(res.getByTestId('tableHeaderCell_Severity_7')).toBeInTheDocument(); }); - it('Renders only bulk delete on status all', async () => { + it('should render the case stats', () => { const wrapper = mount( ); - - wrapper.find('[data-test-subj*="checkboxSelectRow-"]').first().simulate('click'); - wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); - - await waitFor(() => { - expect(wrapper.find('[data-test-subj="cases-bulk-open-button"]').exists()).toEqual(false); - expect(wrapper.find('[data-test-subj="cases-bulk-in-progress-button"]').exists()).toEqual( - false - ); - expect(wrapper.find('[data-test-subj="cases-bulk-close-button"]').exists()).toEqual(false); - expect( - wrapper.find('[data-test-subj="cases-bulk-delete-button"]').first().props().disabled - ).toEqual(true); - }); - }); - - it('Bulk close status update', async () => { - const updateCasesSpy = jest.spyOn(api, 'updateCases'); - - const result = appMockRenderer.render(); - const theCase = useGetCasesMockState.data.cases[0]; - userEvent.click(result.getByTestId('case-status-filter')); - await waitForEuiPopoverOpen(); - userEvent.click(result.getByTestId('case-status-filter-in-progress')); - userEvent.click(result.getByTestId(`checkboxSelectRow-${theCase.id}`)); - userEvent.click(result.getByText('Bulk actions')); - await waitForEuiPopoverOpen(); - userEvent.click(result.getByTestId('cases-bulk-close-button')); - await waitFor(() => {}); - - expect(updateCasesSpy).toBeCalledWith( - [{ id: theCase.id, version: theCase.version, status: CaseStatuses.closed }], - expect.anything() - ); - }); - - it('Bulk open status update', async () => { - const updateCasesSpy = jest.spyOn(api, 'updateCases'); - - const result = appMockRenderer.render(); - const theCase = useGetCasesMockState.data.cases[0]; - userEvent.click(result.getByTestId('case-status-filter')); - await waitForEuiPopoverOpen(); - userEvent.click(result.getByTestId('case-status-filter-closed')); - userEvent.click(result.getByTestId(`checkboxSelectRow-${theCase.id}`)); - userEvent.click(result.getByText('Bulk actions')); - await waitForEuiPopoverOpen(); - userEvent.click(result.getByTestId('cases-bulk-open-button')); - await waitFor(() => {}); - - expect(updateCasesSpy).toBeCalledWith( - [{ id: theCase.id, version: theCase.version, status: CaseStatuses.open }], - expect.anything() - ); - }); - - it('Bulk in-progress status update', async () => { - const updateCasesSpy = jest.spyOn(api, 'updateCases'); - - const result = appMockRenderer.render(); - const theCase = useGetCasesMockState.data.cases[0]; - userEvent.click(result.getByTestId('case-status-filter')); - await waitForEuiPopoverOpen(); - userEvent.click(result.getByTestId('case-status-filter-closed')); - userEvent.click(result.getByTestId(`checkboxSelectRow-${theCase.id}`)); - userEvent.click(result.getByText('Bulk actions')); - await waitForEuiPopoverOpen(); - userEvent.click(result.getByTestId('cases-bulk-in-progress-button')); - await waitFor(() => {}); - - expect(updateCasesSpy).toBeCalledWith( - [{ id: theCase.id, version: theCase.version, status: CaseStatuses['in-progress'] }], - expect.anything() - ); + expect(wrapper.find('[data-test-subj="cases-count-stats"]')).toBeTruthy(); }); it('should not render table utility bar when isSelectorView=true', async () => { @@ -522,60 +363,21 @@ describe('AllCasesListGeneric', () => { }); it('should call onRowClick when clicking a case with modal=true', async () => { + const theCase = defaultGetCases.data.cases[0]; + const wrapper = mount( ); - wrapper.find('[data-test-subj="cases-table-row-select-1"]').first().simulate('click'); + wrapper + .find(`[data-test-subj="cases-table-row-select-${theCase.id}"]`) + .first() + .simulate('click'); + await waitFor(() => { - expect(onRowClick).toHaveBeenCalledWith({ - assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }], - closedAt: null, - closedBy: null, - comments: [], - connector: { fields: null, id: '123', name: 'My Connector', type: '.jira' }, - createdAt: '2020-02-19T23:06:33.798Z', - createdBy: { - email: 'leslie.knope@elastic.co', - fullName: 'Leslie Knope', - username: 'lknope', - }, - description: 'Security banana Issue', - severity: CaseSeverity.LOW, - duration: null, - externalService: { - connectorId: '123', - connectorName: 'connector name', - externalId: 'external_id', - externalTitle: 'external title', - externalUrl: 'basicPush.com', - pushedAt: '2020-02-20T15:02:57.995Z', - pushedBy: { - email: 'leslie.knope@elastic.co', - fullName: 'Leslie Knope', - username: 'lknope', - }, - }, - id: '1', - owner: SECURITY_SOLUTION_OWNER, - status: 'open', - tags: ['coke', 'pepsi'], - title: 'Another horrible breach!!', - totalAlerts: 0, - totalComment: 0, - updatedAt: '2020-02-20T15:02:57.995Z', - updatedBy: { - email: 'leslie.knope@elastic.co', - fullName: 'Leslie Knope', - username: 'lknope', - }, - version: 'WzQ3LDFd', - settings: { - syncAlerts: true, - }, - }); + expect(onRowClick).toHaveBeenCalledWith(theCase); }); }); @@ -591,7 +393,7 @@ describe('AllCasesListGeneric', () => { }); }); - it('should change the status to closed', async () => { + it('should filter by status: closed', async () => { const result = appMockRenderer.render(); userEvent.click(result.getByTestId('case-status-filter')); await waitForEuiPopoverOpen(); @@ -610,7 +412,7 @@ describe('AllCasesListGeneric', () => { }); }); - it('should change the status to in-progress', async () => { + it('should filter by status: in-progress', async () => { const result = appMockRenderer.render(); userEvent.click(result.getByTestId('case-status-filter')); await waitForEuiPopoverOpen(); @@ -629,7 +431,7 @@ describe('AllCasesListGeneric', () => { }); }); - it('should change the status to open', async () => { + it('should filter by status: open', async () => { const result = appMockRenderer.render(); userEvent.click(result.getByTestId('case-status-filter')); await waitForEuiPopoverOpen(); @@ -668,34 +470,7 @@ describe('AllCasesListGeneric', () => { }); }); - it('should not render status when isSelectorView=true', async () => { - const wrapper = mount( - - - - ); - - const { result } = renderHook( - () => - useCasesColumns({ - ...defaultColumnArgs, - isSelectorView: true, - }), - { - wrapper: ({ children }) => {children}, - } - ); - - expect(result.current.find((i) => i.name === 'Status')).toBeFalsy(); - - await waitFor(() => { - expect(wrapper.find('[data-test-subj="cases-table"]').exists()).toBeTruthy(); - }); - - expect(wrapper.find('[data-test-subj="case-view-status-dropdown"]').exists()).toBeFalsy(); - }); - - it.skip('renders the first available status when hiddenStatus is given', async () => { + it('renders the first available status when hiddenStatus is given', async () => { const wrapper = mount( @@ -926,6 +701,338 @@ describe('AllCasesListGeneric', () => { }); }); }); + + describe('Actions', () => { + const updateCasesSpy = jest.spyOn(api, 'updateCases'); + const deleteCasesSpy = jest.spyOn(api, 'deleteCases'); + + describe('Bulk actions', () => { + it('Renders bulk action', async () => { + const result = appMockRenderer.render(); + + act(() => { + userEvent.click(result.getByTestId('checkboxSelectAll')); + }); + + act(() => { + userEvent.click(result.getByText('Bulk actions')); + }); + + await waitForEuiPopoverOpen(); + + expect(result.getByTestId('case-bulk-action-status')).toBeInTheDocument(); + expect(result.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + }); + + it.each([[CaseStatuses.open], [CaseStatuses['in-progress']], [CaseStatuses.closed]])( + 'Bulk update status: %s', + async (status) => { + const result = appMockRenderer.render(); + + act(() => { + userEvent.click(result.getByTestId('checkboxSelectAll')); + }); + + act(() => { + userEvent.click(result.getByText('Bulk actions')); + }); + + await waitForEuiPopoverOpen(); + + act(() => { + userEvent.click(result.getByTestId('case-bulk-action-status')); + }); + + await waitFor(() => { + expect(result.getByTestId(`cases-bulk-action-status-${status}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(result.getByTestId(`cases-bulk-action-status-${status}`)); + }); + + await waitForComponentToUpdate(); + + expect(updateCasesSpy).toBeCalledWith( + useGetCasesMockState.data.cases.map(({ id, version }) => ({ + id, + version, + status, + })), + expect.anything() + ); + } + ); + + it.each([ + [CaseSeverity.LOW], + [CaseSeverity.MEDIUM], + [CaseSeverity.HIGH], + [CaseSeverity.CRITICAL], + ])('Bulk update severity: %s', async (severity) => { + const result = appMockRenderer.render(); + + act(() => { + userEvent.click(result.getByTestId('checkboxSelectAll')); + }); + + act(() => { + userEvent.click(result.getByText('Bulk actions')); + }); + + await waitForEuiPopoverOpen(); + + act(() => { + userEvent.click(result.getByTestId('case-bulk-action-severity')); + }); + + await waitFor(() => { + expect(result.getByTestId(`cases-bulk-action-severity-${severity}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(result.getByTestId(`cases-bulk-action-severity-${severity}`)); + }); + + await waitForComponentToUpdate(); + + expect(updateCasesSpy).toBeCalledWith( + useGetCasesMockState.data.cases.map(({ id, version }) => ({ + id, + version, + severity, + })), + expect.anything() + ); + }); + + it('Bulk delete', async () => { + const result = appMockRenderer.render(); + + act(() => { + userEvent.click(result.getByTestId('checkboxSelectAll')); + }); + + act(() => { + userEvent.click(result.getByText('Bulk actions')); + }); + + await waitForEuiPopoverOpen(); + + act(() => { + userEvent.click(result.getByTestId('cases-bulk-action-delete'), undefined, { + skipPointerEventsCheck: true, + }); + }); + + await waitFor(() => { + expect(result.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(result.getByTestId('confirmModalConfirmButton')); + }); + + await waitFor(() => { + expect(deleteCasesSpy).toHaveBeenCalledWith( + [ + 'basic-case-id', + '1', + '2', + '3', + '4', + 'case-with-alerts-id', + 'case-with-alerts-syncoff-id', + 'case-with-registered-attachment', + ], + expect.anything() + ); + }); + }); + + it('should disable the checkboxes when the user has read only permissions', async () => { + appMockRenderer = createAppMockRenderer({ permissions: readCasesPermissions() }); + const res = appMockRenderer.render(); + + expect(res.getByTestId('checkboxSelectAll')).toBeDisabled(); + + await waitFor(() => { + for (const theCase of defaultGetCases.data.cases) { + expect(res.getByTestId(`checkboxSelectRow-${theCase.id}`)).toBeDisabled(); + } + }); + }); + }); + + describe('Row actions', () => { + const statusTests = [ + [CaseStatuses.open], + [CaseStatuses['in-progress']], + [CaseStatuses.closed], + ]; + + const severityTests = [ + [CaseSeverity.LOW], + [CaseSeverity.MEDIUM], + [CaseSeverity.HIGH], + [CaseSeverity.CRITICAL], + ]; + + it('should render row actions', async () => { + const res = appMockRenderer.render(); + + await waitFor(() => { + for (const theCase of defaultGetCases.data.cases) { + expect(res.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); + } + }); + }); + + it.each(statusTests)('update the status of a case: %s', async (status) => { + const res = appMockRenderer.render(); + const openCase = useGetCasesMockState.data.cases[0]; + const inProgressCase = useGetCasesMockState.data.cases[1]; + const theCase = status === CaseStatuses.open ? inProgressCase : openCase; + + await waitFor(() => { + expect(res.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${theCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId(`case-action-status-panel-${theCase.id}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`case-action-status-panel-${theCase.id}`), undefined, { + skipPointerEventsCheck: true, + }); + }); + + await waitFor(() => { + expect(res.getByTestId(`cases-bulk-action-status-${status}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`cases-bulk-action-status-${status}`)); + }); + + await waitFor(() => { + expect(updateCasesSpy).toHaveBeenCalledWith( + [{ id: theCase.id, status, version: theCase.version }], + expect.anything() + ); + }); + }); + + it.each(severityTests)('update the status of a case: %s', async (severity) => { + const res = appMockRenderer.render(); + const lowCase = useGetCasesMockState.data.cases[0]; + const mediumCase = useGetCasesMockState.data.cases[1]; + const theCase = severity === CaseSeverity.LOW ? mediumCase : lowCase; + + await waitFor(() => { + expect(res.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${theCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId(`case-action-severity-panel-${theCase.id}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`case-action-severity-panel-${theCase.id}`), undefined, { + skipPointerEventsCheck: true, + }); + }); + + await waitFor(() => { + expect(res.getByTestId(`cases-bulk-action-severity-${severity}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`cases-bulk-action-severity-${severity}`)); + }); + + await waitFor(() => { + expect(updateCasesSpy).toHaveBeenCalledWith( + [{ id: theCase.id, severity, version: theCase.version }], + expect.anything() + ); + }); + }); + + it('should delete a case', async () => { + const res = appMockRenderer.render(); + const theCase = defaultGetCases.data.cases[0]; + + await waitFor(() => { + expect(res.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${theCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-delete'), undefined, { + skipPointerEventsCheck: true, + }); + }); + + await waitFor(() => { + expect(res.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('confirmModalConfirmButton')); + }); + + await waitFor(() => { + expect(deleteCasesSpy).toHaveBeenCalledWith(['basic-case-id'], expect.anything()); + }); + }); + + it('should disable row actions when bulk selecting all cases', async () => { + const res = appMockRenderer.render(); + + act(() => { + userEvent.click(res.getByTestId('checkboxSelectAll')); + }); + + await waitFor(() => { + for (const theCase of defaultGetCases.data.cases) { + expect(res.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeDisabled(); + } + }); + }); + + it('should disable row actions when selecting a case', async () => { + const res = appMockRenderer.render(); + const caseToSelect = defaultGetCases.data.cases[0]; + + act(() => { + userEvent.click(res.getByTestId(`checkboxSelectRow-${caseToSelect.id}`)); + }); + + await waitFor(() => { + for (const theCase of defaultGetCases.data.cases) { + expect(res.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeDisabled(); + } + }); + }); + }); + }); }); describe('Assignees', () => { diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx index c7b2d4895f94..0d2cff95c491 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx @@ -21,7 +21,7 @@ import { import { CaseStatuses, caseStatuses } from '../../../common/api'; import { useAvailableCasesOwners } from '../app/use_available_owners'; -import { useCasesColumns } from './columns'; +import { useCasesColumns } from './use_cases_columns'; import { CasesTableFilters } from './table_filters'; import { EuiBasicTableOnChange } from './types'; @@ -37,7 +37,7 @@ import { } from '../../containers/use_get_cases'; import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles'; import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; -import { getAllPermissionsExceptFrom } from '../../utils/permissions'; +import { getAllPermissionsExceptFrom, isReadOnlyPermissions } from '../../utils/permissions'; import { useIsLoadingCases } from './use_is_loading_cases'; const ProgressLoader = styled(EuiProgress)` @@ -196,13 +196,7 @@ export const AllCasesList = React.memo( [deselectCases, hasOwner, availableSolutions, owner] ); - /** - * At the time of changing this from all to delete the only bulk action we have is to delete. When we add more - * actions we'll need to revisit this to allow more granular checks around the bulk actions. - */ - const showActions = permissions.delete && !isSelectorView; - - const columns = useCasesColumns({ + const { columns } = useCasesColumns({ filterStatus: filterOptions.status ?? StatusAll, userProfiles: userProfiles ?? new Map(), currentUserProfile, @@ -210,6 +204,7 @@ export const AllCasesList = React.memo( connectors, onRowClick, showSolutionColumn: !hasOwner && availableSolutions.length > 1, + disableActions: selectedCases.length > 0, }); const pagination = useMemo( @@ -226,8 +221,9 @@ export const AllCasesList = React.memo( () => ({ onSelectionChange: setSelectedCases, initialSelected: selectedCases, + selectable: () => !isReadOnlyPermissions(permissions), }), - [selectedCases, setSelectedCases] + [permissions, selectedCases] ); const isDataEmpty = useMemo(() => data.total === 0, [data]); @@ -272,7 +268,6 @@ export const AllCasesList = React.memo( ( pagination={pagination} selectedCases={selectedCases} selection={euiBasicTableSelectionProps} - showActions={showActions} sorting={sorting} tableRef={tableRef} tableRowProps={tableRowProps} diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx deleted file mode 100644 index 7dec0d793791..000000000000 --- a/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { mount } from 'enzyme'; - -import '../../common/mock/match_media'; -import { ExternalServiceColumn } from './columns'; -import { useGetCasesMockState } from '../../containers/mock'; -import { connectors } from '../configure_cases/__mock__'; -import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock'; - -describe('ExternalServiceColumn ', () => { - let appMockRender: AppMockRenderer; - - beforeEach(() => { - jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); - }); - - it('Not pushed render', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper.find(`[data-test-subj="case-table-column-external-notPushed"]`).last().exists() - ).toBeTruthy(); - }); - - it('Up to date', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper.find(`[data-test-subj="case-table-column-external-upToDate"]`).last().exists() - ).toBeTruthy(); - }); - - it('Needs update', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper.find(`[data-test-subj="case-table-column-external-requiresUpdate"]`).last().exists() - ).toBeTruthy(); - }); - - it('it does not throw when accessing the icon if the connector type is not registered', () => { - // If the component throws the test will fail - expect(() => - mount( - - - - ) - ).not.toThrowError(); - }); - - it('shows the connectors icon if the user has read access to actions', async () => { - const result = appMockRender.render( - - ); - - expect(result.getByTestId('cases-table-connector-icon')).toBeInTheDocument(); - }); - - it('hides the connectors icon if the user does not have read access to actions', async () => { - appMockRender.coreStart.application.capabilities = { - ...appMockRender.coreStart.application.capabilities, - actions: { save: false, show: false }, - }; - - const result = appMockRender.render( - - ); - - expect(result.queryByTestId('cases-table-connector-icon')).toBe(null); - }); -}); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx index 98c89215aac2..e26831266d65 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx @@ -6,13 +6,14 @@ */ import React from 'react'; -import { mount } from 'enzyme'; import { AllCasesSelectorModal } from '.'; -import { TestProviders } from '../../../common/mock'; -import { AllCasesList } from '../all_cases_list'; +import { AppMockRenderer, createAppMockRenderer } from '../../../common/mock'; +import userEvent from '@testing-library/user-event'; +import { act, waitFor } from '@testing-library/react'; -jest.mock('../all_cases_list', () => ({ AllCasesList: jest.fn().mockReturnValue(<>) })); +jest.mock('../../../containers/api'); +jest.mock('../../../containers/user_profiles/api'); const onRowClick = jest.fn(); const defaultProps = { @@ -20,48 +21,73 @@ const defaultProps = { }; describe('AllCasesSelectorModal', () => { + let appMockRenderer: AppMockRenderer; + beforeEach(() => { jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); }); it('renders', () => { - const wrapper = mount( - - - - ); + const res = appMockRenderer.render(); + + expect(res.getByTestId('all-cases-modal')).toBeInTheDocument(); + }); + + it('Closing modal when pressing the x icon', () => { + const res = appMockRenderer.render(); + + act(() => { + userEvent.click(res.getByLabelText('Closes this modal window')); + }); + + expect(res.queryByTestId('all-cases-modal')).toBeFalsy(); + }); + + it('Closing modal when pressing the cancel button', () => { + const res = appMockRenderer.render(); - expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeTruthy(); + act(() => { + userEvent.click(res.getByTestId('all-cases-modal-cancel-button')); + }); + + expect(res.queryByTestId('all-cases-modal')).toBeFalsy(); + }); + + it('should not show bulk actions and row actions on the modal', async () => { + const res = appMockRenderer.render(); + await waitFor(() => { + expect(res.getByTestId('cases-table')).toBeInTheDocument(); + }); + + expect(res.queryByTestId('case-table-bulk-actions-link-icon')).toBeFalsy(); + expect(res.queryByText('Actions')).toBeFalsy(); }); - it('Closing modal calls onCloseCaseModal', () => { - const wrapper = mount( - - - - ); + it('should show the select button', async () => { + const res = appMockRenderer.render(); + await waitFor(() => { + expect(res.getByTestId('cases-table')).toBeInTheDocument(); + }); - wrapper.find('.euiModal__closeIcon').first().simulate('click'); - expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeFalsy(); + expect(res.getAllByTestId(/cases-table-row-select/).length).toBeGreaterThan(0); }); - it('pass the correct props to getAllCases method', () => { - const fullProps = { - ...defaultProps, - hiddenStatuses: [], - }; - - mount( - - - - ); - - expect((AllCasesList as unknown as jest.Mock).mock.calls[0][0]).toEqual( - expect.objectContaining({ - hiddenStatuses: fullProps.hiddenStatuses, - isSelectorView: true, - }) - ); + it('should hide the metrics', async () => { + const res = appMockRenderer.render(); + await waitFor(() => { + expect(res.getByTestId('cases-table')).toBeInTheDocument(); + }); + + expect(res.queryByTestId('cases-metrics-stats')).toBeFalsy(); + }); + + it('should show the create case button', async () => { + const res = appMockRenderer.render(); + await waitFor(() => { + expect(res.getByTestId('cases-table')).toBeInTheDocument(); + }); + + expect(res.getByTestId('cases-table-add-case-filter-bar')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx index fccdf76abd2c..f5007fcd5a09 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx @@ -68,7 +68,11 @@ export const AllCasesSelectorModal = React.memo( /> - + {i18n.CANCEL} diff --git a/x-pack/plugins/cases/public/components/all_cases/table.tsx b/x-pack/plugins/cases/public/components/all_cases/table.tsx index 208c26e47648..1f7382351e80 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table.tsx @@ -19,7 +19,7 @@ import styled from 'styled-components'; import { CasesTableUtilityBar } from './utility_bar'; import { LinkButton } from '../links'; -import { Cases, Case, FilterOptions } from '../../../common/ui/types'; +import { Cases, Case } from '../../../common/ui/types'; import * as i18n from './translations'; import { useCreateCaseNavigation } from '../../common/navigation'; import { useCasesContext } from '../cases_context/use_cases_context'; @@ -27,7 +27,6 @@ import { useCasesContext } from '../cases_context/use_cases_context'; interface CasesTableProps { columns: EuiBasicTableProps['columns']; data: Cases; - filterOptions: FilterOptions; goToCreateCase?: () => void; isCasesLoading: boolean; isCommentUpdating: boolean; @@ -37,7 +36,6 @@ interface CasesTableProps { pagination: Pagination; selectedCases: Case[]; selection: EuiTableSelectionType; - showActions: boolean; sorting: EuiBasicTableProps['sorting']; tableRef: MutableRefObject; tableRowProps: EuiBasicTableProps['rowProps']; @@ -51,7 +49,6 @@ const Div = styled.div` export const CasesTable: FunctionComponent = ({ columns, data, - filterOptions, goToCreateCase, isCasesLoading, isCommentUpdating, @@ -61,7 +58,6 @@ export const CasesTable: FunctionComponent = ({ pagination, selectedCases, selection, - showActions, sorting, tableRef, tableRowProps, @@ -86,11 +82,10 @@ export const CasesTable: FunctionComponent = ({
) : ( -
+ <> @@ -98,7 +93,7 @@ export const CasesTable: FunctionComponent = ({ className={classnames({ isSelectorView })} columns={columns} data-test-subj="cases-table" - isSelectable={showActions} + isSelectable={!isSelectorView} itemId="id" items={data.cases} loading={isCommentUpdating} @@ -128,10 +123,11 @@ export const CasesTable: FunctionComponent = ({ pagination={pagination} ref={tableRef} rowProps={tableRowProps} - selection={showActions ? selection : undefined} + selection={!isSelectorView ? selection : undefined} sorting={sorting} + hasActions={false} /> -
+ ); }; CasesTable.displayName = 'CasesTable'; diff --git a/x-pack/plugins/cases/public/components/all_cases/translations.ts b/x-pack/plugins/cases/public/components/all_cases/translations.ts index 96a683aee507..332c0d493101 100644 --- a/x-pack/plugins/cases/public/components/all_cases/translations.ts +++ b/x-pack/plugins/cases/public/components/all_cases/translations.ts @@ -130,40 +130,3 @@ export const TOTAL_ASSIGNEES_FILTERED = (total: number) => defaultMessage: '{total, plural, one {# assignee} other {# assignees}} filtered', values: { total }, }); - -export const CLOSED_CASES = ({ - totalCases, - caseTitle, -}: { - totalCases: number; - caseTitle?: string; -}) => - i18n.translate('xpack.cases.containers.closedCases', { - values: { caseTitle, totalCases }, - defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', - }); - -export const REOPENED_CASES = ({ - totalCases, - caseTitle, -}: { - totalCases: number; - caseTitle?: string; -}) => - i18n.translate('xpack.cases.containers.reopenedCases', { - values: { caseTitle, totalCases }, - defaultMessage: 'Opened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', - }); - -export const MARK_IN_PROGRESS_CASES = ({ - totalCases, - caseTitle, -}: { - totalCases: number; - caseTitle?: string; -}) => - i18n.translate('xpack.cases.containers.markInProgressCases', { - values: { caseTitle, totalCases }, - defaultMessage: - 'Marked {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}} as in progress', - }); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx new file mode 100644 index 000000000000..bf2994a0cad5 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx @@ -0,0 +1,318 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import userEvent from '@testing-library/user-event'; +import { waitFor } from '@testing-library/dom'; +import { act, renderHook } from '@testing-library/react-hooks'; + +import { useActions } from './use_actions'; +import { basicCase } from '../../containers/mock'; +import * as api from '../../containers/api'; +import { + AppMockRenderer, + createAppMockRenderer, + noDeleteCasesPermissions, + onlyDeleteCasesPermission, + allCasesPermissions, + readCasesPermissions, +} from '../../common/mock'; + +jest.mock('../../containers/api'); + +describe('useActions', () => { + let appMockRender: AppMockRenderer; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders column actions', async () => { + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "actions": Object { + "align": "right", + "name": "Actions", + "render": [Function], + }, + } + `); + }); + + it('renders the popover', async () => { + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + expect(res.getByTestId(`case-action-popover-${basicCase.id}`)).toBeInTheDocument(); + }); + + it('open the action popover', async () => { + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByText('Actions')).toBeInTheDocument(); + expect(res.getByTestId(`case-action-status-panel-${basicCase.id}`)).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + }); + }); + + it('change the status of the case', async () => { + const updateCasesSpy = jest.spyOn(api, 'updateCases'); + + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId(`case-action-status-panel-${basicCase.id}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`case-action-status-panel-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId('cases-bulk-action-status-open')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-status-in-progress')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-status-closed')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-status-in-progress')); + }); + + await waitFor(() => { + expect(updateCasesSpy).toHaveBeenCalled(); + }); + }); + + it('change the severity of the case', async () => { + const updateCasesSpy = jest.spyOn(api, 'updateCases'); + + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId(`case-action-severity-panel-${basicCase.id}`)).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId(`case-action-severity-panel-${basicCase.id}`), undefined, { + skipPointerEventsCheck: true, + }); + }); + + await waitFor(() => { + expect(res.getByTestId('cases-bulk-action-severity-low')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-severity-medium')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-severity-high')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-severity-critical')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-severity-medium')); + }); + + await waitFor(() => { + expect(updateCasesSpy).toHaveBeenCalled(); + }); + }); + + describe('Modals', () => { + it('delete a case', async () => { + const deleteSpy = jest.spyOn(api, 'deleteCases'); + + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-delete'), undefined, { + skipPointerEventsCheck: true, + }); + }); + + await waitFor(() => { + expect(res.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('confirmModalConfirmButton')); + }); + + await waitFor(() => { + expect(deleteSpy).toHaveBeenCalled(); + }); + }); + + it('closes the modal', async () => { + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-delete'), undefined, { + skipPointerEventsCheck: true, + }); + }); + + await waitFor(() => { + expect(res.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('confirmModalCancelButton'), undefined, { + skipPointerEventsCheck: true, + }); + }); + + expect(res.queryByTestId('confirm-delete-case-modal')).toBeFalsy(); + }); + }); + + describe('Permissions', () => { + it('shows the correct actions with all permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: allCasesPermissions() }); + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId(`case-action-status-panel-${basicCase.id}`)).toBeInTheDocument(); + expect(res.getByTestId(`case-action-severity-panel-${basicCase.id}`)).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + expect(res.getByTestId(`actions-separator-${basicCase.id}`)).toBeInTheDocument(); + }); + }); + + it('shows the correct actions with no delete permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: noDeleteCasesPermissions() }); + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.getByTestId(`case-action-status-panel-${basicCase.id}`)).toBeInTheDocument(); + expect(res.getByTestId(`case-action-severity-panel-${basicCase.id}`)).toBeInTheDocument(); + expect(res.queryByTestId('cases-bulk-action-delete')).toBeFalsy(); + expect(res.queryByTestId(`actions-separator-${basicCase.id}`)).toBeFalsy(); + }); + }); + + it('shows the correct actions with only delete permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + act(() => { + userEvent.click(res.getByTestId(`case-action-popover-button-${basicCase.id}`)); + }); + + await waitFor(() => { + expect(res.queryByTestId(`case-action-status-panel-${basicCase.id}`)).toBeFalsy(); + expect(res.queryByTestId(`case-action-severity-panel-${basicCase.id}`)).toBeFalsy(); + expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + expect(res.queryByTestId(`actions-separator-${basicCase.id}`)).toBeFalsy(); + }); + }); + + it('returns null if the user does not have update or delete permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: readCasesPermissions() }); + const { result } = renderHook(() => useActions({ disableActions: false }), { + wrapper: appMockRender.AppWrapper, + }); + + expect(result.current.actions).toBe(null); + }); + + it('disables the action correctly', async () => { + appMockRender = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); + const { result } = renderHook(() => useActions({ disableActions: true }), { + wrapper: appMockRender.AppWrapper, + }); + + const comp = result.current.actions!.render(basicCase) as React.ReactElement; + const res = appMockRender.render(comp); + + await waitFor(() => { + expect(res.getByTestId(`case-action-popover-button-${basicCase.id}`)).toBeDisabled(); + }); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx b/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx new file mode 100644 index 000000000000..1fe7450835e8 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo, useState } from 'react'; +import { + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiContextMenuPanelItemDescriptor, + EuiPopover, + EuiTableComputedColumnType, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { Case } from '../../containers/types'; +import { useDeleteAction } from '../actions/delete/use_delete_action'; +import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; +import { useStatusAction } from '../actions/status/use_status_action'; +import { useRefreshCases } from './use_on_refresh_cases'; +import * as i18n from './translations'; +import { statuses } from '../status'; +import { useCasesContext } from '../cases_context/use_cases_context'; +import { useSeverityAction } from '../actions/severity/use_severity_action'; +import { severities } from '../severity/config'; + +const ActionColumnComponent: React.FC<{ theCase: Case; disableActions: boolean }> = ({ + theCase, + disableActions, +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const tooglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); + const refreshCases = useRefreshCases(); + + const deleteAction = useDeleteAction({ + isDisabled: false, + onAction: closePopover, + onActionSuccess: refreshCases, + }); + + const statusAction = useStatusAction({ + isDisabled: false, + onAction: closePopover, + onActionSuccess: refreshCases, + selectedStatus: theCase.status, + }); + + const severityAction = useSeverityAction({ + isDisabled: false, + onAction: closePopover, + onActionSuccess: refreshCases, + selectedSeverity: theCase.severity, + }); + + const canDelete = deleteAction.canDelete; + const canUpdate = statusAction.canUpdateStatus; + + const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { + const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; + const panelsToBuild: EuiContextMenuPanelDescriptor[] = [ + { id: 0, items: mainPanelItems, title: i18n.ACTIONS }, + ]; + + if (canUpdate) { + mainPanelItems.push({ + name: ( + {statuses[theCase.status]?.label ?? '-'} }} + /> + ), + panel: 1, + disabled: !canUpdate, + key: `case-action-status-panel-${theCase.id}`, + 'data-test-subj': `case-action-status-panel-${theCase.id}`, + }); + + mainPanelItems.push({ + name: ( + {severities[theCase.severity]?.label ?? '-'} }} + /> + ), + panel: 2, + disabled: !canUpdate, + key: `case-action-severity-panel-${theCase.id}`, + 'data-test-subj': `case-action-severity-panel-${theCase.id}`, + }); + } + + /** + * A separator is added if a) there is one item above + * and b) there is an item below. For this to happen the + * user has to have delete and update permissions + */ + if (canUpdate && canDelete) { + mainPanelItems.push({ + isSeparator: true, + key: `actions-separator-${theCase.id}`, + 'data-test-subj': `actions-separator-${theCase.id}`, + }); + } + + if (canDelete) { + mainPanelItems.push(deleteAction.getAction([theCase])); + } + + if (canUpdate) { + panelsToBuild.push({ + id: 1, + title: i18n.STATUS, + items: statusAction.getActions([theCase]), + }); + + panelsToBuild.push({ + id: 2, + title: i18n.SEVERITY, + items: severityAction.getActions([theCase]), + }); + } + + return panelsToBuild; + }, [canDelete, canUpdate, deleteAction, severityAction, statusAction, theCase]); + + return ( + <> + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + {deleteAction.isModalVisible ? ( + + ) : null} + + ); +}; + +ActionColumnComponent.displayName = 'ActionColumnComponent'; + +const ActionColumn = React.memo(ActionColumnComponent); + +interface UseBulkActionsReturnValue { + actions: EuiTableComputedColumnType | null; +} + +interface UseBulkActionsProps { + disableActions: boolean; +} + +export const useActions = ({ disableActions }: UseBulkActionsProps): UseBulkActionsReturnValue => { + const { permissions } = useCasesContext(); + const shouldShowActions = permissions.update || permissions.delete; + + return { + actions: shouldShowActions + ? { + name: i18n.ACTIONS, + align: 'right', + render: (theCase: Case) => { + return ( + + ); + }, + } + : null, + }; +}; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.test.tsx new file mode 100644 index 000000000000..cb4c473204ab --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.test.tsx @@ -0,0 +1,419 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiContextMenu } from '@elastic/eui'; +import userEvent from '@testing-library/user-event'; +import { waitFor } from '@testing-library/react'; +import { act, renderHook } from '@testing-library/react-hooks'; + +import { + allCasesPermissions, + AppMockRenderer, + createAppMockRenderer, + noDeleteCasesPermissions, + onlyDeleteCasesPermission, +} from '../../common/mock'; +import { useBulkActions } from './use_bulk_actions'; +import * as api from '../../containers/api'; +import { basicCase } from '../../containers/mock'; + +jest.mock('../../containers/api'); + +describe('useBulkActions', () => { + let appMockRender: AppMockRenderer; + const onAction = jest.fn(); + const onActionSuccess = jest.fn(); + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + describe('Panels', () => { + it('renders bulk actions', async () => { + const { result } = renderHook( + () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "modals": , + "panels": Array [ + Object { + "id": 0, + "items": Array [ + Object { + "data-test-subj": "case-bulk-action-status", + "disabled": false, + "key": "case-bulk-action-status", + "name": "Status", + "panel": 1, + }, + Object { + "data-test-subj": "case-bulk-action-severity", + "disabled": false, + "key": "case-bulk-action-severity", + "name": "Severity", + "panel": 2, + }, + Object { + "data-test-subj": "bulk-actions-separator", + "isSeparator": true, + "key": "bulk-actions-separator", + }, + Object { + "data-test-subj": "cases-bulk-action-delete", + "disabled": false, + "icon": , + "key": "cases-bulk-action-delete", + "name": + Delete case + , + "onClick": [Function], + }, + ], + "title": "Actions", + }, + Object { + "id": 1, + "items": Array [ + Object { + "data-test-subj": "cases-bulk-action-status-open", + "disabled": true, + "icon": "empty", + "key": "cases-bulk-action-status-open", + "name": "Open", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-status-in-progress", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-action-status-in-progress", + "name": "In progress", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-status-closed", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-status-action", + "name": "Closed", + "onClick": [Function], + }, + ], + "title": "Status", + }, + Object { + "id": 2, + "items": Array [ + Object { + "data-test-subj": "cases-bulk-action-severity-low", + "disabled": true, + "icon": "empty", + "key": "cases-bulk-action-severity-low", + "name": "Low", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-severity-medium", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-action-severity-medium", + "name": "Medium", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-severity-high", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-action-severity-high", + "name": "High", + "onClick": [Function], + }, + Object { + "data-test-subj": "cases-bulk-action-severity-critical", + "disabled": false, + "icon": "empty", + "key": "cases-bulk-action-severity-critical", + "name": "Critical", + "onClick": [Function], + }, + ], + "title": "Severity", + }, + ], + } + `); + }); + + it('change the status of cases', async () => { + const updateCasesSpy = jest.spyOn(api, 'updateCases'); + + const { result, waitFor: waitForHook } = renderHook( + () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const modals = result.current.modals; + const panels = result.current.panels; + + const res = appMockRender.render( + <> + + {modals} + + ); + + act(() => { + userEvent.click(res.getByTestId('case-bulk-action-status')); + }); + + await waitFor(() => { + expect(res.getByTestId('cases-bulk-action-status-open')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-status-in-progress')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-status-closed')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-status-in-progress')); + }); + + await waitForHook(() => { + expect(updateCasesSpy).toHaveBeenCalled(); + }); + }); + + it('change the severity of cases', async () => { + const updateCasesSpy = jest.spyOn(api, 'updateCases'); + + const { result, waitFor: waitForHook } = renderHook( + () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const modals = result.current.modals; + const panels = result.current.panels; + + const res = appMockRender.render( + <> + + {modals} + + ); + + act(() => { + userEvent.click(res.getByTestId('case-bulk-action-severity')); + }); + + await waitFor(() => { + expect(res.getByTestId('cases-bulk-action-severity-low')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-severity-medium')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-severity-high')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-severity-critical')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-severity-medium')); + }); + + await waitForHook(() => { + expect(updateCasesSpy).toHaveBeenCalled(); + }); + }); + + describe('Modals', () => { + it('delete a case', async () => { + const deleteSpy = jest.spyOn(api, 'deleteCases'); + + const { result, waitFor: waitForHook } = renderHook( + () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + let modals = result.current.modals; + const panels = result.current.panels; + + const res = appMockRender.render( + <> + + {modals} + + ); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-delete')); + }); + + modals = result.current.modals; + res.rerender( + <> + + {modals} + + ); + + await waitFor(() => { + expect(res.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('confirmModalConfirmButton')); + }); + + await waitForHook(() => { + expect(deleteSpy).toHaveBeenCalled(); + }); + }); + + it('closes the modal', async () => { + const { result } = renderHook( + () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + let modals = result.current.modals; + const panels = result.current.panels; + + const res = appMockRender.render( + <> + + {modals} + + ); + + act(() => { + userEvent.click(res.getByTestId('cases-bulk-action-delete')); + }); + + modals = result.current.modals; + res.rerender( + <> + + {modals} + + ); + + await waitFor(() => { + expect(res.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + }); + + act(() => { + userEvent.click(res.getByTestId('confirmModalCancelButton')); + }); + + modals = result.current.modals; + res.rerender( + <> + + {modals} + + ); + + expect(res.queryByTestId('confirm-delete-case-modal')).toBeFalsy(); + }); + }); + }); + + describe('Permissions', () => { + it('shows the correct actions with all permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: allCasesPermissions() }); + const { result, waitFor: waitForHook } = renderHook( + () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const modals = result.current.modals; + const panels = result.current.panels; + + const res = appMockRender.render( + <> + + {modals} + + ); + + await waitForHook(() => { + expect(res.getByTestId('case-bulk-action-status')).toBeInTheDocument(); + expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + expect(res.getByTestId('bulk-actions-separator')).toBeInTheDocument(); + }); + }); + + it('shows the correct actions with no delete permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: noDeleteCasesPermissions() }); + const { result, waitFor: waitForHook } = renderHook( + () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const modals = result.current.modals; + const panels = result.current.panels; + + const res = appMockRender.render( + <> + + {modals} + + ); + + await waitForHook(() => { + expect(res.getByTestId('case-bulk-action-status')).toBeInTheDocument(); + expect(res.queryByTestId('cases-bulk-action-delete')).toBeFalsy(); + expect(res.queryByTestId('bulk-actions-separator')).toBeFalsy(); + }); + }); + + it('shows the correct actions with only delete permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); + const { result, waitFor: waitForHook } = renderHook( + () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + const modals = result.current.modals; + const panels = result.current.panels; + + const res = appMockRender.render( + <> + + {modals} + + ); + + await waitForHook(() => { + expect(res.queryByTestId('case-bulk-action-status')).toBeFalsy(); + expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + expect(res.queryByTestId('bulk-actions-separator')).toBeFalsy(); + }); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.tsx b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.tsx new file mode 100644 index 000000000000..dce7d136148d --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.tsx @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { Case } from '../../containers/types'; +import { useDeleteAction } from '../actions/delete/use_delete_action'; +import { useSeverityAction } from '../actions/severity/use_severity_action'; +import { useStatusAction } from '../actions/status/use_status_action'; +import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; +import * as i18n from './translations'; + +interface UseBulkActionsProps { + selectedCases: Case[]; + onAction: () => void; + onActionSuccess: () => void; +} + +interface UseBulkActionsReturnValue { + panels: EuiContextMenuPanelDescriptor[]; + modals: JSX.Element; +} + +export const useBulkActions = ({ + selectedCases, + onAction, + onActionSuccess, +}: UseBulkActionsProps): UseBulkActionsReturnValue => { + const isDisabled = selectedCases.length === 0; + + const deleteAction = useDeleteAction({ + isDisabled, + onAction, + onActionSuccess, + }); + + const statusAction = useStatusAction({ + isDisabled, + onAction, + onActionSuccess, + }); + + const severityAction = useSeverityAction({ + isDisabled, + onAction, + onActionSuccess, + }); + + const canDelete = deleteAction.canDelete; + const canUpdate = statusAction.canUpdateStatus; + + const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { + const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; + const panelsToBuild: EuiContextMenuPanelDescriptor[] = [ + { id: 0, items: mainPanelItems, title: i18n.ACTIONS }, + ]; + + if (canUpdate) { + mainPanelItems.push({ + name: i18n.STATUS, + panel: 1, + disabled: isDisabled, + 'data-test-subj': 'case-bulk-action-status', + key: 'case-bulk-action-status', + }); + + mainPanelItems.push({ + name: i18n.SEVERITY, + panel: 2, + disabled: isDisabled, + 'data-test-subj': 'case-bulk-action-severity', + key: 'case-bulk-action-severity', + }); + } + + /** + * A separator is added if a) there is one item above + * and b) there is an item below. For this to happen the + * user has to have delete and update permissions + */ + if (canUpdate && canDelete) { + mainPanelItems.push({ + isSeparator: true as const, + key: 'bulk-actions-separator', + 'data-test-subj': 'bulk-actions-separator', + }); + } + + if (canDelete) { + mainPanelItems.push(deleteAction.getAction(selectedCases)); + } + + if (canUpdate) { + panelsToBuild.push({ + id: 1, + title: i18n.STATUS, + items: statusAction.getActions(selectedCases), + }); + + panelsToBuild.push({ + id: 2, + title: i18n.SEVERITY, + items: severityAction.getActions(selectedCases), + }); + } + + return panelsToBuild; + }, [canDelete, canUpdate, deleteAction, isDisabled, selectedCases, severityAction, statusAction]); + + return { + modals: ( + <> + {deleteAction.isModalVisible ? ( + + ) : null} + + ), + panels, + }; +}; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx new file mode 100644 index 000000000000..85caa0b0348d --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx @@ -0,0 +1,679 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; + +import '../../common/mock/match_media'; +import { ExternalServiceColumn, GetCasesColumn, useCasesColumns } from './use_cases_columns'; +import { useGetCasesMockState } from '../../containers/mock'; +import { connectors } from '../configure_cases/__mock__'; +import { + AppMockRenderer, + createAppMockRenderer, + readCasesPermissions, + TestProviders, +} from '../../common/mock'; +import { renderHook } from '@testing-library/react-hooks'; +import { CaseStatuses } from '../../../common'; +import { userProfilesMap, userProfiles } from '../../containers/user_profiles/api.mock'; + +describe('useCasesColumns ', () => { + let appMockRender: AppMockRenderer; + const useCasesColumnsProps: GetCasesColumn = { + filterStatus: CaseStatuses.open, + userProfiles: userProfilesMap, + currentUserProfile: userProfiles[0], + isSelectorView: false, + showSolutionColumn: true, + }; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('return all columns correctly', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + + appMockRender = createAppMockRenderer({ license }); + + const { result } = renderHook(() => useCasesColumns(useCasesColumnsProps), { + wrapper: appMockRender.AppWrapper, + }); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "columns": Array [ + Object { + "name": "Name", + "render": [Function], + }, + Object { + "field": "assignees", + "name": "Assignees", + "render": [Function], + }, + Object { + "field": "tags", + "name": "Tags", + "render": [Function], + "truncateText": true, + }, + Object { + "align": "right", + "field": "totalAlerts", + "name": "Alerts", + "render": [Function], + }, + Object { + "align": "right", + "field": "owner", + "name": "Solution", + "render": [Function], + }, + Object { + "align": "right", + "field": "totalComment", + "name": "Comments", + "render": [Function], + }, + Object { + "field": "createdAt", + "name": "Created on", + "render": [Function], + "sortable": true, + }, + Object { + "name": "External Incident", + "render": [Function], + }, + Object { + "name": "Status", + "render": [Function], + }, + Object { + "name": "Severity", + "render": [Function], + }, + Object { + "align": "right", + "name": "Actions", + "render": [Function], + }, + ], + } + `); + }); + + it('does not render the solution columns', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + + appMockRender = createAppMockRenderer({ license }); + + const { result } = renderHook( + () => useCasesColumns({ ...useCasesColumnsProps, showSolutionColumn: false }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "columns": Array [ + Object { + "name": "Name", + "render": [Function], + }, + Object { + "field": "assignees", + "name": "Assignees", + "render": [Function], + }, + Object { + "field": "tags", + "name": "Tags", + "render": [Function], + "truncateText": true, + }, + Object { + "align": "right", + "field": "totalAlerts", + "name": "Alerts", + "render": [Function], + }, + Object { + "align": "right", + "field": "totalComment", + "name": "Comments", + "render": [Function], + }, + Object { + "field": "createdAt", + "name": "Created on", + "render": [Function], + "sortable": true, + }, + Object { + "name": "External Incident", + "render": [Function], + }, + Object { + "name": "Status", + "render": [Function], + }, + Object { + "name": "Severity", + "render": [Function], + }, + Object { + "align": "right", + "name": "Actions", + "render": [Function], + }, + ], + } + `); + }); + + it('does not return the alerts column', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + + appMockRender = createAppMockRenderer({ license, features: { alerts: { enabled: false } } }); + + const { result } = renderHook(() => useCasesColumns(useCasesColumnsProps), { + wrapper: appMockRender.AppWrapper, + }); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "columns": Array [ + Object { + "name": "Name", + "render": [Function], + }, + Object { + "field": "assignees", + "name": "Assignees", + "render": [Function], + }, + Object { + "field": "tags", + "name": "Tags", + "render": [Function], + "truncateText": true, + }, + Object { + "align": "right", + "field": "owner", + "name": "Solution", + "render": [Function], + }, + Object { + "align": "right", + "field": "totalComment", + "name": "Comments", + "render": [Function], + }, + Object { + "field": "createdAt", + "name": "Created on", + "render": [Function], + "sortable": true, + }, + Object { + "name": "External Incident", + "render": [Function], + }, + Object { + "name": "Status", + "render": [Function], + }, + Object { + "name": "Severity", + "render": [Function], + }, + Object { + "align": "right", + "name": "Actions", + "render": [Function], + }, + ], + } + `); + }); + + it('does not return the assignees column', async () => { + const { result } = renderHook(() => useCasesColumns(useCasesColumnsProps), { + wrapper: appMockRender.AppWrapper, + }); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "columns": Array [ + Object { + "name": "Name", + "render": [Function], + }, + Object { + "field": "tags", + "name": "Tags", + "render": [Function], + "truncateText": true, + }, + Object { + "align": "right", + "field": "totalAlerts", + "name": "Alerts", + "render": [Function], + }, + Object { + "align": "right", + "field": "owner", + "name": "Solution", + "render": [Function], + }, + Object { + "align": "right", + "field": "totalComment", + "name": "Comments", + "render": [Function], + }, + Object { + "field": "createdAt", + "name": "Created on", + "render": [Function], + "sortable": true, + }, + Object { + "name": "External Incident", + "render": [Function], + }, + Object { + "name": "Status", + "render": [Function], + }, + Object { + "name": "Severity", + "render": [Function], + }, + Object { + "align": "right", + "name": "Actions", + "render": [Function], + }, + ], + } + `); + }); + + it('shows the closedAt column if the filterStatus=closed', async () => { + appMockRender = createAppMockRenderer(); + + const { result } = renderHook( + () => useCasesColumns({ ...useCasesColumnsProps, filterStatus: CaseStatuses.closed }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "columns": Array [ + Object { + "name": "Name", + "render": [Function], + }, + Object { + "field": "tags", + "name": "Tags", + "render": [Function], + "truncateText": true, + }, + Object { + "align": "right", + "field": "totalAlerts", + "name": "Alerts", + "render": [Function], + }, + Object { + "align": "right", + "field": "owner", + "name": "Solution", + "render": [Function], + }, + Object { + "align": "right", + "field": "totalComment", + "name": "Comments", + "render": [Function], + }, + Object { + "field": "closedAt", + "name": "Closed on", + "render": [Function], + "sortable": true, + }, + Object { + "name": "External Incident", + "render": [Function], + }, + Object { + "name": "Status", + "render": [Function], + }, + Object { + "name": "Severity", + "render": [Function], + }, + Object { + "align": "right", + "name": "Actions", + "render": [Function], + }, + ], + } + `); + }); + + it('shows the select button if isSelectorView=true', async () => { + const { result } = renderHook( + () => useCasesColumns({ ...useCasesColumnsProps, isSelectorView: true }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "columns": Array [ + Object { + "name": "Name", + "render": [Function], + }, + Object { + "field": "tags", + "name": "Tags", + "render": [Function], + "truncateText": true, + }, + Object { + "align": "right", + "field": "totalAlerts", + "name": "Alerts", + "render": [Function], + }, + Object { + "align": "right", + "field": "owner", + "name": "Solution", + "render": [Function], + }, + Object { + "align": "right", + "field": "totalComment", + "name": "Comments", + "render": [Function], + }, + Object { + "field": "createdAt", + "name": "Created on", + "render": [Function], + "sortable": true, + }, + Object { + "name": "External Incident", + "render": [Function], + }, + Object { + "name": "Status", + "render": [Function], + }, + Object { + "name": "Severity", + "render": [Function], + }, + Object { + "align": "right", + "render": [Function], + }, + ], + } + `); + }); + + it('does not shows the actions if isSelectorView=true', async () => { + const { result } = renderHook( + () => useCasesColumns({ ...useCasesColumnsProps, isSelectorView: true }), + { + wrapper: appMockRender.AppWrapper, + } + ); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "columns": Array [ + Object { + "name": "Name", + "render": [Function], + }, + Object { + "field": "tags", + "name": "Tags", + "render": [Function], + "truncateText": true, + }, + Object { + "align": "right", + "field": "totalAlerts", + "name": "Alerts", + "render": [Function], + }, + Object { + "align": "right", + "field": "owner", + "name": "Solution", + "render": [Function], + }, + Object { + "align": "right", + "field": "totalComment", + "name": "Comments", + "render": [Function], + }, + Object { + "field": "createdAt", + "name": "Created on", + "render": [Function], + "sortable": true, + }, + Object { + "name": "External Incident", + "render": [Function], + }, + Object { + "name": "Status", + "render": [Function], + }, + Object { + "name": "Severity", + "render": [Function], + }, + Object { + "align": "right", + "render": [Function], + }, + ], + } + `); + }); + + it('does not shows the actions if the user does not have the right permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: readCasesPermissions() }); + + const { result } = renderHook(() => useCasesColumns(useCasesColumnsProps), { + wrapper: appMockRender.AppWrapper, + }); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "columns": Array [ + Object { + "name": "Name", + "render": [Function], + }, + Object { + "field": "tags", + "name": "Tags", + "render": [Function], + "truncateText": true, + }, + Object { + "align": "right", + "field": "totalAlerts", + "name": "Alerts", + "render": [Function], + }, + Object { + "align": "right", + "field": "owner", + "name": "Solution", + "render": [Function], + }, + Object { + "align": "right", + "field": "totalComment", + "name": "Comments", + "render": [Function], + }, + Object { + "field": "createdAt", + "name": "Created on", + "render": [Function], + "sortable": true, + }, + Object { + "name": "External Incident", + "render": [Function], + }, + Object { + "name": "Status", + "render": [Function], + }, + Object { + "name": "Severity", + "render": [Function], + }, + ], + } + `); + }); + + describe('ExternalServiceColumn ', () => { + it('Not pushed render', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find(`[data-test-subj="case-table-column-external-notPushed"]`).last().exists() + ).toBeTruthy(); + }); + + it('Up to date', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find(`[data-test-subj="case-table-column-external-upToDate"]`).last().exists() + ).toBeTruthy(); + }); + + it('Needs update', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find(`[data-test-subj="case-table-column-external-requiresUpdate"]`).last().exists() + ).toBeTruthy(); + }); + + it('it does not throw when accessing the icon if the connector type is not registered', () => { + // If the component throws the test will fail + expect(() => + mount( + + + + ) + ).not.toThrowError(); + }); + + it('shows the connectors icon if the user has read access to actions', async () => { + const result = appMockRender.render( + + ); + + expect(result.getByTestId('cases-table-connector-icon')).toBeInTheDocument(); + }); + + it('hides the connectors icon if the user does not have read access to actions', async () => { + appMockRender.coreStart.application.capabilities = { + ...appMockRender.coreStart.application.capabilities, + actions: { save: false, show: false }, + }; + + const result = appMockRender.render( + + ); + + expect(result.queryByTestId('cases-table-connector-icon')).toBe(null); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx similarity index 50% rename from x-pack/plugins/cases/public/components/all_cases/columns.tsx rename to x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx index 948abdbbcd2f..dc90fcfe6c4a 100644 --- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback } from 'react'; import { EuiBadgeGroup, EuiBadge, @@ -24,7 +24,7 @@ import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import styled from 'styled-components'; import { UserProfileWithAvatar } from '@kbn/user-profile-components'; -import { Case, UpdateByKey } from '../../../common/ui/types'; +import { Case } from '../../../common/ui/types'; import { CaseStatuses, ActionConnector, CaseSeverity } from '../../../common/api'; import { OWNER_INFO } from '../../../common/constants'; import { getEmptyTagValue } from '../empty_value'; @@ -32,26 +32,21 @@ import { FormattedRelativePreferenceDate } from '../formatted_date'; import { CaseDetailsLink } from '../links'; import * as i18n from './translations'; import { ALERTS } from '../../common/translations'; -import { getActions } from './actions'; -import { useDeleteCases } from '../../containers/use_delete_cases'; -import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; +import { useActions } from './use_actions'; import { useApplicationCapabilities, useKibana } from '../../common/lib/kibana'; -import { StatusContextMenu } from '../case_action_bar/status_context_menu'; import { TruncatedText } from '../truncated_text'; import { getConnectorIcon } from '../utils'; import type { CasesOwners } from '../../client/helpers/can_use_cases'; import { severities } from '../severity/config'; -import { useUpdateCase } from '../../containers/use_update_case'; -import { useCasesContext } from '../cases_context/use_cases_context'; import { UserToolTip } from '../user_profiles/user_tooltip'; import { useAssignees } from '../../containers/user_profiles/use_assignees'; import { getUsernameDataTestSubj } from '../user_profiles/data_test_subject'; import { CurrentUserProfile } from '../types'; import { SmallUserAvatar } from '../user_profiles/small_user_avatar'; import { useCasesFeatures } from '../../common/use_cases_features'; -import { useRefreshCases } from './use_on_refresh_cases'; +import { Status } from '../status'; -export type CasesColumns = +type CasesColumns = | EuiTableActionsColumnType | EuiTableComputedColumnType | EuiTableFieldDataColumnType; @@ -107,9 +102,14 @@ export interface GetCasesColumn { isSelectorView: boolean; connectors?: ActionConnector[]; onRowClick?: (theCase: Case) => void; - showSolutionColumn?: boolean; + disableActions?: boolean; +} + +export interface UseCasesColumnsReturnValue { + columns: CasesColumns[]; } + export const useCasesColumns = ({ filterStatus, userProfiles, @@ -118,57 +118,10 @@ export const useCasesColumns = ({ connectors = [], onRowClick, showSolutionColumn, -}: GetCasesColumn): CasesColumns[] => { - const [isModalVisible, setIsModalVisible] = useState(false); - const { mutate: deleteCases } = useDeleteCases(); - const refreshCases = useRefreshCases(); + disableActions = false, +}: GetCasesColumn): UseCasesColumnsReturnValue => { const { isAlertsEnabled, caseAssignmentAuthorized } = useCasesFeatures(); - const { permissions } = useCasesContext(); - const [caseToBeDeleted, setCaseToBeDeleted] = useState(); - const { updateCaseProperty, isLoading: isLoadingUpdateCase } = useUpdateCase(); - - const closeModal = useCallback(() => setIsModalVisible(false), []); - const openModal = useCallback(() => setIsModalVisible(true), []); - - const onDeleteAction = useCallback( - (theCase: Case) => { - openModal(); - setCaseToBeDeleted(theCase.id); - }, - [openModal] - ); - - const onConfirmDeletion = useCallback(() => { - closeModal(); - if (caseToBeDeleted) { - deleteCases({ - caseIds: [caseToBeDeleted], - successToasterTitle: i18n.DELETED_CASES(1), - }); - } - }, [caseToBeDeleted, closeModal, deleteCases]); - - const handleDispatchUpdate = useCallback( - ({ updateKey, updateValue, caseData }: UpdateByKey) => { - updateCaseProperty({ - updateKey, - updateValue, - caseData, - onSuccess: () => { - refreshCases(); - }, - }); - }, - [refreshCases, updateCaseProperty] - ); - - const actions = useMemo( - () => - getActions({ - deleteCaseOnClick: onDeleteAction, - }), - [onDeleteAction] - ); + const { actions } = useActions({ disableActions }); const assignCaseAction = useCallback( async (theCase: Case) => { @@ -179,7 +132,7 @@ export const useCasesColumns = ({ [onRowClick] ); - return [ + const columns: CasesColumns[] = [ { name: i18n.NAME, render: (theCase: Case) => { @@ -205,129 +158,134 @@ export const useCasesColumns = ({ return getEmptyTagValue(); }, }, - ...(caseAssignmentAuthorized - ? [ - { - field: 'assignees', - name: i18n.ASSIGNEES, - render: (assignees: Case['assignees']) => ( - - ), - }, - ] - : []), - { - field: 'tags', - name: i18n.TAGS, - render: (tags: Case['tags']) => { - if (tags != null && tags.length > 0) { - const badges = ( - - {tags.map((tag: string, i: number) => ( - - {tag} - - ))} - - ); + ]; + + if (caseAssignmentAuthorized) { + columns.push({ + field: 'assignees', + name: i18n.ASSIGNEES, + render: (assignees: Case['assignees']) => ( + + ), + }); + } + + columns.push({ + field: 'tags', + name: i18n.TAGS, + render: (tags: Case['tags']) => { + if (tags != null && tags.length > 0) { + const badges = ( + + {tags.map((tag: string, i: number) => ( + + {tag} + + ))} + + ); + + return ( + + {badges} + + ); + } + return getEmptyTagValue(); + }, + truncateText: true, + }); + + if (isAlertsEnabled) { + columns.push({ + align: RIGHT_ALIGNMENT, + field: 'totalAlerts', + name: ALERTS, + render: (totalAlerts: Case['totalAlerts']) => + totalAlerts != null + ? renderStringField(`${totalAlerts}`, `case-table-column-alertsCount`) + : getEmptyTagValue(), + }); + } + + if (showSolutionColumn) { + columns.push({ + align: RIGHT_ALIGNMENT, + field: 'owner', + name: i18n.SOLUTION, + render: (caseOwner: CasesOwners) => { + const ownerInfo = OWNER_INFO[caseOwner]; + return ownerInfo ? ( + + ) : ( + getEmptyTagValue() + ); + }, + }); + } + columns.push({ + align: RIGHT_ALIGNMENT, + field: 'totalComment', + name: i18n.COMMENTS, + render: (totalComment: Case['totalComment']) => + totalComment != null + ? renderStringField(`${totalComment}`, `case-table-column-commentCount`) + : getEmptyTagValue(), + }); + + if (filterStatus === CaseStatuses.closed) { + columns.push({ + field: 'closedAt', + name: i18n.CLOSED_ON, + sortable: true, + render: (closedAt: Case['closedAt']) => { + if (closedAt != null) { return ( - - {badges} - + + + ); } return getEmptyTagValue(); }, - truncateText: true, - }, - ...(isAlertsEnabled - ? [ - { - align: RIGHT_ALIGNMENT, - field: 'totalAlerts', - name: ALERTS, - render: (totalAlerts: Case['totalAlerts']) => - totalAlerts != null - ? renderStringField(`${totalAlerts}`, `case-table-column-alertsCount`) - : getEmptyTagValue(), - }, - ] - : []), - ...(showSolutionColumn - ? [ - { - align: RIGHT_ALIGNMENT, - field: 'owner', - name: i18n.SOLUTION, - render: (caseOwner: CasesOwners) => { - const ownerInfo = OWNER_INFO[caseOwner]; - return ownerInfo ? ( - - ) : ( - getEmptyTagValue() - ); - }, - }, - ] - : []), - { - align: RIGHT_ALIGNMENT, - field: 'totalComment', - name: i18n.COMMENTS, - render: (totalComment: Case['totalComment']) => - totalComment != null - ? renderStringField(`${totalComment}`, `case-table-column-commentCount`) - : getEmptyTagValue(), - }, - filterStatus === CaseStatuses.closed - ? { - field: 'closedAt', - name: i18n.CLOSED_ON, - sortable: true, - render: (closedAt: Case['closedAt']) => { - if (closedAt != null) { - return ( - - - - ); - } - return getEmptyTagValue(); - }, + }); + } else { + columns.push({ + field: 'createdAt', + name: i18n.CREATED_ON, + sortable: true, + render: (createdAt: Case['createdAt']) => { + if (createdAt != null) { + return ( + + + + ); } - : { - field: 'createdAt', - name: i18n.CREATED_ON, - sortable: true, - render: (createdAt: Case['createdAt']) => { - if (createdAt != null) { - return ( - - - - ); - } - return getEmptyTagValue(); - }, - }, + return getEmptyTagValue(); + }, + }); + } + + columns.push( { name: i18n.EXTERNAL_INCIDENT, render: (theCase: Case) => { @@ -337,91 +295,63 @@ export const useCasesColumns = ({ return getEmptyTagValue(); }, }, - ...(!isSelectorView - ? [ - { - name: i18n.STATUS, - render: (theCase: Case) => { - if (theCase.status === null || theCase.status === undefined) { - return getEmptyTagValue(); - } - - return ( - - handleDispatchUpdate({ - updateKey: 'status', - updateValue: status, - caseData: theCase, - }) - } - /> - ); - }, - }, - ] - : []), + { + name: i18n.STATUS, + render: (theCase: Case) => { + if (theCase.status === null || theCase.status === undefined) { + return getEmptyTagValue(); + } + + return ; + }, + }, { name: i18n.SEVERITY, render: (theCase: Case) => { if (theCase.severity != null) { const severityData = severities[theCase.severity ?? CaseSeverity.LOW]; return ( - + {severityData.label} ); } return getEmptyTagValue(); }, - }, + } + ); - ...(isSelectorView - ? [ - { - align: RIGHT_ALIGNMENT, - render: (theCase: Case) => { - if (theCase.id != null) { - return ( - { - assignCaseAction(theCase); - }} - size="s" - fill={true} - > - {i18n.SELECT} - - ); - } - return getEmptyTagValue(); - }, - }, - ] - : []), - ...(permissions.delete && !isSelectorView - ? [ - { - name: ( - <> - {i18n.ACTIONS} - {isModalVisible ? ( - - ) : null} - - ), - actions, - }, - ] - : []), - ]; + if (isSelectorView) { + columns.push({ + align: RIGHT_ALIGNMENT, + render: (theCase: Case) => { + if (theCase.id != null) { + return ( + { + assignCaseAction(theCase); + }} + size="s" + fill={true} + > + {i18n.SELECT} + + ); + } + return getEmptyTagValue(); + }, + }); + } + + if (!isSelectorView && actions) { + columns.push(actions); + } + + return { columns }; }; interface Props { diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.test.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.test.tsx new file mode 100644 index 000000000000..3a8769460656 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.test.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { + noCasesPermissions, + onlyDeleteCasesPermission, + AppMockRenderer, + createAppMockRenderer, + writeCasesPermissions, +} from '../../common/mock'; +import { casesQueriesKeys } from '../../containers/constants'; +import { basicCase } from '../../containers/mock'; +import { CasesTableUtilityBar } from './utility_bar'; + +describe('Severity form field', () => { + let appMockRender: AppMockRenderer; + const deselectCases = jest.fn(); + + const props = { + totalCases: 5, + selectedCases: [basicCase], + deselectCases, + }; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + }); + + it('renders', async () => { + const result = appMockRender.render(); + expect(result.getByText('Showing 5 cases')).toBeInTheDocument(); + expect(result.getByText('Selected 1 case')).toBeInTheDocument(); + expect(result.getByTestId('case-table-bulk-actions-link-icon')).toBeInTheDocument(); + expect(result.getByTestId('all-cases-refresh-link-icon')).toBeInTheDocument(); + }); + + it('opens the bulk actions correctly', async () => { + const result = appMockRender.render(); + + act(() => { + userEvent.click(result.getByTestId('case-table-bulk-actions-link-icon')); + }); + + await waitFor(() => { + expect(result.getByTestId('case-table-bulk-actions-context-menu')); + }); + }); + + it('closes the bulk actions correctly', async () => { + const result = appMockRender.render(); + + act(() => { + userEvent.click(result.getByTestId('case-table-bulk-actions-link-icon')); + }); + + await waitFor(() => { + expect(result.getByTestId('case-table-bulk-actions-context-menu')); + }); + + act(() => { + userEvent.click(result.getByTestId('case-table-bulk-actions-link-icon')); + }); + + await waitFor(() => { + expect(result.queryByTestId('case-table-bulk-actions-context-menu')).toBeFalsy(); + }); + }); + + it('refresh correctly', async () => { + const result = appMockRender.render(); + const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); + + act(() => { + userEvent.click(result.getByTestId('all-cases-refresh-link-icon')); + }); + + await waitFor(() => { + expect(deselectCases).toHaveBeenCalled(); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); + }); + }); + + it('does not show the bulk actions without update & delete permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: noCasesPermissions() }); + const result = appMockRender.render(); + + expect(result.queryByTestId('case-table-bulk-actions-link-icon')).toBeFalsy(); + }); + + it('does show the bulk actions with only delete permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); + const result = appMockRender.render(); + + expect(result.getByTestId('case-table-bulk-actions-link-icon')).toBeInTheDocument(); + }); + + it('does show the bulk actions with update permissions', async () => { + appMockRender = createAppMockRenderer({ permissions: writeCasesPermissions() }); + const result = appMockRender.render(); + + expect(result.getByTestId('case-table-bulk-actions-link-icon')).toBeInTheDocument(); + }); + + it('does not show the bulk actions if there are not selected cases', async () => { + const result = appMockRender.render(); + + expect(result.queryByTestId('case-table-bulk-actions-link-icon')).toBeFalsy(); + expect(result.queryByText('Showing 0 cases')).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx index fdfcdc17d472..6daf9cb66511 100644 --- a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx @@ -6,152 +6,137 @@ */ import React, { FunctionComponent, useCallback, useState } from 'react'; -import { EuiContextMenuPanel } from '@elastic/eui'; -import { CaseStatuses } from '../../../common'; import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '../utility_bar'; + EuiButtonEmpty, + EuiContextMenu, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiText, + useEuiTheme, +} from '@elastic/eui'; import * as i18n from './translations'; -import { Cases, Case, FilterOptions } from '../../../common/ui/types'; -import { getBulkItems } from '../bulk_actions'; -import { useDeleteCases } from '../../containers/use_delete_cases'; -import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; -import { useUpdateCases } from '../../containers/use_bulk_update_case'; +import { Case } from '../../../common/ui/types'; import { useRefreshCases } from './use_on_refresh_cases'; +import { useBulkActions } from './use_bulk_actions'; +import { useCasesContext } from '../cases_context/use_cases_context'; interface Props { - data: Cases; - enableBulkActions: boolean; - filterOptions: FilterOptions; + isSelectorView?: boolean; + totalCases: number; selectedCases: Case[]; deselectCases: () => void; } -export const getStatusToasterMessage = (status: CaseStatuses, cases: Case[]): string => { - const totalCases = cases.length; - const caseTitle = totalCases === 1 ? cases[0].title : ''; +export const CasesTableUtilityBar: FunctionComponent = React.memo( + ({ isSelectorView, totalCases, selectedCases, deselectCases }) => { + const { euiTheme } = useEuiTheme(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); + const refreshCases = useRefreshCases(); + const { permissions } = useCasesContext(); - if (status === CaseStatuses.open) { - return i18n.REOPENED_CASES({ totalCases, caseTitle }); - } else if (status === CaseStatuses['in-progress']) { - return i18n.MARK_IN_PROGRESS_CASES({ totalCases, caseTitle }); - } else if (status === CaseStatuses.closed) { - return i18n.CLOSED_CASES({ totalCases, caseTitle }); - } - - return ''; -}; - -export const CasesTableUtilityBar: FunctionComponent = ({ - data, - enableBulkActions = false, - filterOptions, - selectedCases, - deselectCases, -}) => { - const [isModalVisible, setIsModalVisible] = useState(false); - const onCloseModal = useCallback(() => setIsModalVisible(false), []); - const refreshCases = useRefreshCases(); - - const { mutate: deleteCases } = useDeleteCases(); - const { mutate: updateCases } = useUpdateCases(); - - const toggleBulkDeleteModal = useCallback((cases: Case[]) => { - setIsModalVisible(true); - }, []); - - const handleUpdateCaseStatus = useCallback( - (status: CaseStatuses) => { - const casesToUpdate = selectedCases.map((theCase) => ({ - status, - id: theCase.id, - version: theCase.version, - })); + const onRefresh = useCallback(() => { + deselectCases(); + refreshCases(); + }, [deselectCases, refreshCases]); - updateCases({ - cases: casesToUpdate, - successToasterTitle: getStatusToasterMessage(status, selectedCases), - }); - }, - [selectedCases, updateCases] - ); - - const getBulkItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - - ), - [selectedCases, filterOptions.status, toggleBulkDeleteModal, handleUpdateCaseStatus] - ); - - const onConfirmDeletion = useCallback(() => { - setIsModalVisible(false); - deleteCases({ - caseIds: selectedCases.map(({ id }) => id), - successToasterTitle: i18n.DELETED_CASES(selectedCases.length), + const { panels, modals } = useBulkActions({ + selectedCases, + onAction: closePopover, + onActionSuccess: onRefresh, }); - }, [deleteCases, selectedCases]); - - const onRefresh = useCallback(() => { - deselectCases(); - refreshCases(); - }, [deselectCases, refreshCases]); - return ( - - - - - {i18n.SHOWING_CASES(data.total ?? 0)} - - - - {enableBulkActions && ( - <> - - {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} - + /** + * At least update or delete permissions needed to show bulk actions. + * Granular permission check for each action is performed + * in the useBulkActions hook. + */ + const showBulkActions = (permissions.update || permissions.delete) && selectedCases.length > 0; - - {i18n.BULK_ACTIONS} - - - )} - + + - {i18n.REFRESH} - - - - {isModalVisible ? ( - - ) : null} - - ); -}; + + {i18n.SHOWING_CASES(totalCases)} + + + + + {!isSelectorView && showBulkActions && ( + <> + + + {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} + + + + + {i18n.BULK_ACTIONS} + + } + > + + + + + )} + + + {i18n.REFRESH} + + + + + + {modals} + + ); + } +); + CasesTableUtilityBar.displayName = 'CasesTableUtilityBar'; diff --git a/x-pack/plugins/cases/public/components/bulk_actions/index.tsx b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx deleted file mode 100644 index fcf2002f8882..000000000000 --- a/x-pack/plugins/cases/public/components/bulk_actions/index.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { EuiContextMenuItem } from '@elastic/eui'; - -import { CaseStatusWithAllStatus } from '../../../common/ui/types'; -import { CaseStatuses } from '../../../common/api'; -import { statuses } from '../status'; -import * as i18n from './translations'; -import { Case } from '../../containers/types'; - -interface GetBulkItems { - caseStatus: CaseStatusWithAllStatus; - closePopover: () => void; - deleteCasesAction: (cases: Case[]) => void; - selectedCases: Case[]; - updateCaseStatus: (status: CaseStatuses) => void; -} - -export const getBulkItems = ({ - caseStatus, - closePopover, - deleteCasesAction, - selectedCases, - updateCaseStatus, -}: GetBulkItems) => { - let statusMenuItems: JSX.Element[] = []; - - const openMenuItem = ( - { - closePopover(); - updateCaseStatus(CaseStatuses.open); - }} - > - {statuses[CaseStatuses.open].actions.bulk.title} - - ); - - const inProgressMenuItem = ( - { - closePopover(); - updateCaseStatus(CaseStatuses['in-progress']); - }} - > - {statuses[CaseStatuses['in-progress']].actions.bulk.title} - - ); - - const closeMenuItem = ( - { - closePopover(); - updateCaseStatus(CaseStatuses.closed); - }} - > - {statuses[CaseStatuses.closed].actions.bulk.title} - - ); - - switch (caseStatus) { - case CaseStatuses.open: - statusMenuItems = [inProgressMenuItem, closeMenuItem]; - break; - - case CaseStatuses['in-progress']: - statusMenuItems = [openMenuItem, closeMenuItem]; - break; - - case CaseStatuses.closed: - statusMenuItems = [openMenuItem, inProgressMenuItem]; - break; - - default: - break; - } - - return [ - ...statusMenuItems, - { - closePopover(); - deleteCasesAction(selectedCases); - }} - > - {i18n.BULK_ACTION_DELETE_SELECTED} - , - ]; -}; diff --git a/x-pack/plugins/cases/public/components/link_icon/index.tsx b/x-pack/plugins/cases/public/components/link_icon/index.tsx index b33529399db9..6285eceed0dd 100644 --- a/x-pack/plugins/cases/public/components/link_icon/index.tsx +++ b/x-pack/plugins/cases/public/components/link_icon/index.tsx @@ -79,6 +79,7 @@ export const LinkIcon = React.memo( } return theChild != null && Object.keys(theChild).length > 0 ? (theChild as string) : ''; }, []); + const aria = useMemo(() => { if (ariaLabel) { return ariaLabel; diff --git a/x-pack/plugins/cases/public/components/status/config.ts b/x-pack/plugins/cases/public/components/status/config.ts index 6c5ff18ad977..520759991605 100644 --- a/x-pack/plugins/cases/public/components/status/config.ts +++ b/x-pack/plugins/cases/public/components/status/config.ts @@ -19,9 +19,6 @@ export const statuses: Statuses = { label: i18n.OPEN, icon: 'folderOpen' as const, actions: { - bulk: { - title: i18n.BULK_ACTION_OPEN_SELECTED, - }, single: { title: i18n.OPEN_CASE, }, @@ -41,9 +38,6 @@ export const statuses: Statuses = { label: i18n.IN_PROGRESS, icon: 'folderExclamation' as const, actions: { - bulk: { - title: i18n.BULK_ACTION_MARK_IN_PROGRESS, - }, single: { title: i18n.MARK_CASE_IN_PROGRESS, }, @@ -63,9 +57,6 @@ export const statuses: Statuses = { label: i18n.CLOSED, icon: 'folderCheck' as const, actions: { - bulk: { - title: i18n.BULK_ACTION_CLOSE_SELECTED, - }, single: { title: i18n.CLOSE_CASE, }, diff --git a/x-pack/plugins/cases/public/components/status/translations.ts b/x-pack/plugins/cases/public/components/status/translations.ts index 4fe75bbcfac7..9401209c51c0 100644 --- a/x-pack/plugins/cases/public/components/status/translations.ts +++ b/x-pack/plugins/cases/public/components/status/translations.ts @@ -40,30 +40,9 @@ export const CASE_CLOSED = i18n.translate('xpack.cases.caseView.caseClosed', { defaultMessage: 'Case closed', }); -export const BULK_ACTION_CLOSE_SELECTED = i18n.translate( - 'xpack.cases.caseTable.bulkActions.closeSelectedTitle', - { - defaultMessage: 'Close selected', - } -); - -export const BULK_ACTION_OPEN_SELECTED = i18n.translate( - 'xpack.cases.caseTable.bulkActions.openSelectedTitle', - { - defaultMessage: 'Open selected', - } -); - export const BULK_ACTION_DELETE_SELECTED = i18n.translate( 'xpack.cases.caseTable.bulkActions.deleteSelectedTitle', { defaultMessage: 'Delete selected', } ); - -export const BULK_ACTION_MARK_IN_PROGRESS = i18n.translate( - 'xpack.cases.caseTable.bulkActions.markInProgressTitle', - { - defaultMessage: 'Mark in progress', - } -); diff --git a/x-pack/plugins/cases/public/components/status/types.ts b/x-pack/plugins/cases/public/components/status/types.ts index 0b4a1184633e..1df8eb781ecc 100644 --- a/x-pack/plugins/cases/public/components/status/types.ts +++ b/x-pack/plugins/cases/public/components/status/types.ts @@ -18,9 +18,6 @@ export type Statuses = Record< label: string; icon: EuiIconType; actions: { - bulk: { - title: string; - }; single: { title: string; description?: string; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap deleted file mode 100644 index f082dc4023e7..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBar it renders 1`] = ` - - - - - Test text - - - - - Test action - - - - - - - Test action - - - - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap deleted file mode 100644 index eb20ac217b30..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBarAction it renders 1`] = ` - - Test action - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap deleted file mode 100644 index 8ef7ee1cfe84..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBarGroup it renders 1`] = ` - - - Test text - - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap deleted file mode 100644 index 2fe3b8ac5c7a..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBarSection it renders 1`] = ` - - - - Test text - - - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap deleted file mode 100644 index cf635ffa49c4..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBarText it renders 1`] = ` - - Test text - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/index.ts b/x-pack/plugins/cases/public/components/utility_bar/index.ts deleted file mode 100644 index 830f3cb043ba..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { UtilityBar } from './utility_bar'; -export { UtilityBarAction } from './utility_bar_action'; -export { UtilityBarGroup } from './utility_bar_group'; -export { UtilityBarSection } from './utility_bar_section'; -export { UtilityBarSpacer } from './utility_bar_spacer'; -export { UtilityBarText } from './utility_bar_text'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/styles.tsx b/x-pack/plugins/cases/public/components/utility_bar/styles.tsx deleted file mode 100644 index 4c4427ba2347..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/styles.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import styled, { css } from 'styled-components'; - -/** - * UTILITY BAR - */ - -export interface BarProps { - border?: boolean; -} - -export interface BarSectionProps { - grow?: boolean; -} - -export interface BarGroupProps { - grow?: boolean; -} - -export const Bar = styled.aside.attrs({ - className: 'casesUtilityBar', -})` - ${({ border, theme }) => css` - ${border && - css` - border-bottom: ${theme.eui.euiBorderThin}; - padding-bottom: ${theme.eui.euiSizeS}; - `} - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) { - display: flex; - justify-content: space-between; - } - `} -`; -Bar.displayName = 'Bar'; - -export const BarSection = styled.div.attrs({ - className: 'casesUtilityBar__section', -})` - ${({ grow, theme }) => css` - & + & { - margin-top: ${theme.eui.euiSizeS}; - } - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { - display: flex; - flex-wrap: wrap; - } - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) { - & + & { - margin-top: 0; - margin-left: ${theme.eui.euiSize}; - } - } - ${grow && - css` - flex: 1; - `} - `} -`; -BarSection.displayName = 'BarSection'; - -export const BarGroup = styled.div.attrs({ - className: 'casesUtilityBar__group', -})` - ${({ grow, theme }) => css` - align-items: flex-start; - display: flex; - flex-wrap: wrap; - - & + & { - margin-top: ${theme.eui.euiSizeS}; - } - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { - border-right: ${theme.eui.euiBorderThin}; - flex-wrap: nowrap; - margin-right: ${theme.eui.euiSizeM}; - padding-right: ${theme.eui.euiSizeM}; - - & + & { - margin-top: 0; - } - - &:last-child { - border-right: none; - margin-right: 0; - padding-right: 0; - } - } - - & > * { - margin-right: ${theme.eui.euiSize}; - - &:last-child { - margin-right: 0; - } - } - ${grow && - css` - flex: 1; - `} - `} -`; -BarGroup.displayName = 'BarGroup'; - -export const BarText = styled.p.attrs({ - className: 'casesUtilityBar__text', -})` - ${({ theme }) => css` - color: ${theme.eui.euiTextSubduedColor}; - font-size: ${theme.eui.euiFontSizeXS}; - line-height: ${theme.eui.euiLineHeight}; - white-space: nowrap; - `} -`; -BarText.displayName = 'BarText'; - -export const BarAction = styled.div.attrs({ - className: 'casesUtilityBar__action', -})` - ${({ theme }) => css` - font-size: ${theme.eui.euiFontSizeXS}; - line-height: ${theme.eui.euiLineHeight}; - `} -`; -BarAction.displayName = 'BarAction'; - -export const BarSpacer = styled.div.attrs({ - className: 'casesUtilityBar__spacer', -})` - ${() => css` - flex: 1; - `} -`; -BarSpacer.displayName = 'BarSpacer'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx deleted file mode 100644 index 62988a7a9dd7..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { euiDarkVars } from '@kbn/ui-theme'; -import { mount, shallow } from 'enzyme'; -import React from 'react'; -import { TestProviders } from '../../common/mock'; - -import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '.'; - -describe('UtilityBar', () => { - test('it renders', () => { - const wrapper = shallow( - - - - - {'Test text'} - - - -

{'Test popover'}

}> - {'Test action'} -
-
-
- - - - {'Test action'} - - -
-
- ); - - expect(wrapper.find('UtilityBar')).toMatchSnapshot(); - }); - - test('it applies border styles when border is true', () => { - const wrapper = mount( - - - - - {'Test text'} - - - -

{'Test popover'}

}> - {'Test action'} -
-
-
- - - - {'Test action'} - - -
-
- ); - const casesUtilityBar = wrapper.find('.casesUtilityBar').first(); - - expect(casesUtilityBar).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(casesUtilityBar).toHaveStyleRule('padding-bottom', euiDarkVars.euiSizeS); - }); - - test('it DOES NOT apply border styles when border is false', () => { - const wrapper = mount( - - - - - {'Test text'} - - - -

{'Test popover'}

}> - {'Test action'} -
-
-
- - - - {'Test action'} - - -
-
- ); - const casesUtilityBar = wrapper.find('.casesUtilityBar').first(); - - expect(casesUtilityBar).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(casesUtilityBar).not.toHaveStyleRule('padding-bottom', euiDarkVars.euiSizeS); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx deleted file mode 100644 index ff47459d437b..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.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 React from 'react'; - -import { Bar, BarProps } from './styles'; - -interface UtilityBarProps extends BarProps { - children: React.ReactNode; -} - -export const UtilityBar = React.memo(({ border, children }) => ( - {children} -)); - -UtilityBar.displayName = 'UtilityBar'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx deleted file mode 100644 index 88977fa9bc58..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mount, shallow } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../common/mock'; -import { UtilityBarAction } from '.'; - -describe('UtilityBarAction', () => { - test('it renders', () => { - const wrapper = shallow( - - {'Test action'} - - ); - - expect(wrapper.find('UtilityBarAction')).toMatchSnapshot(); - }); - - test('it renders a popover', () => { - const wrapper = mount( - -

{'Test popover'}

}> - {'Test action'} -
-
- ); - - expect(wrapper.find('.euiPopover').first().exists()).toBe(true); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx deleted file mode 100644 index e5bed8702149..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiPopover } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; - -import { LinkIcon, LinkIconProps } from '../link_icon'; -import { BarAction } from './styles'; - -const Popover = React.memo( - ({ children, color, iconSide, iconSize, iconType, popoverContent, disabled, ownFocus }) => { - const [popoverState, setPopoverState] = useState(false); - - const closePopover = useCallback(() => setPopoverState(false), [setPopoverState]); - - return ( - setPopoverState(!popoverState)} - disabled={disabled} - > - {children} - - } - closePopover={() => setPopoverState(false)} - isOpen={popoverState} - repositionOnScroll - > - {popoverContent?.(closePopover)} - - ); - } -); - -Popover.displayName = 'Popover'; - -export interface UtilityBarActionProps extends LinkIconProps { - popoverContent?: (closePopover: () => void) => React.ReactNode; - ownFocus?: boolean; - dataTestSubj?: string; -} - -export const UtilityBarAction = React.memo( - ({ - dataTestSubj, - children, - color, - disabled, - href, - iconSide, - iconSize, - iconType, - ownFocus, - onClick, - popoverContent, - }) => ( - - {popoverContent ? ( - - {children} - - ) : ( - - {children} - - )} - - ) -); - -UtilityBarAction.displayName = 'UtilityBarAction'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx deleted file mode 100644 index bf7bb34ab5b6..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../common/mock'; -import { UtilityBarGroup, UtilityBarText } from '.'; - -describe('UtilityBarGroup', () => { - test('it renders', () => { - const wrapper = shallow( - - - {'Test text'} - - - ); - - expect(wrapper.find('UtilityBarGroup')).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx deleted file mode 100644 index ef83d6effc8a..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.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 React from 'react'; - -import { BarGroup, BarGroupProps } from './styles'; - -export interface UtilityBarGroupProps extends BarGroupProps { - children: React.ReactNode; -} - -export const UtilityBarGroup = React.memo(({ grow, children }) => ( - {children} -)); - -UtilityBarGroup.displayName = 'UtilityBarGroup'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx deleted file mode 100644 index 142cca8fc9e3..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../common/mock'; -import { UtilityBarGroup, UtilityBarSection, UtilityBarText } from '.'; - -describe('UtilityBarSection', () => { - test('it renders', () => { - const wrapper = shallow( - - - - {'Test text'} - - - - ); - - expect(wrapper.find('UtilityBarSection')).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx deleted file mode 100644 index c84219cc6348..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.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 React from 'react'; - -import { BarSection, BarSectionProps } from './styles'; - -export interface UtilityBarSectionProps extends BarSectionProps { - children: React.ReactNode; -} - -export const UtilityBarSection = React.memo(({ grow, children }) => ( - {children} -)); - -UtilityBarSection.displayName = 'UtilityBarSection'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx deleted file mode 100644 index 11b3be8d656e..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.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 React from 'react'; - -import { BarSpacer } from './styles'; - -export interface UtilityBarSpacerProps { - dataTestSubj?: string; -} - -export const UtilityBarSpacer = React.memo(({ dataTestSubj }) => ( - -)); - -UtilityBarSpacer.displayName = 'UtilityBarSpacer'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx deleted file mode 100644 index afeae82a19e8..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../common/mock'; -import { UtilityBarText } from '.'; - -describe('UtilityBarText', () => { - test('it renders', () => { - const wrapper = shallow( - - {'Test text'} - - ); - - expect(wrapper.find('UtilityBarText')).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx deleted file mode 100644 index c0be3cbfbe20..000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { BarText } from './styles'; - -export interface UtilityBarTextProps { - children: string | JSX.Element; - dataTestSubj?: string; -} - -export const UtilityBarText = React.memo(({ children, dataTestSubj }) => ( - {children} -)); - -UtilityBarText.displayName = 'UtilityBarText'; diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index 2c0ee9bdc2b0..b7f1ab268a03 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -399,7 +399,14 @@ const basicAction = { export const cases: Case[] = [ basicCase, - { ...pushedCase, id: '1', totalComment: 0, comments: [] }, + { + ...pushedCase, + id: '1', + totalComment: 0, + comments: [], + status: CaseStatuses['in-progress'], + severity: CaseSeverity.MEDIUM, + }, { ...pushedCase, updatedAt: laterTime, id: '2', totalComment: 0, comments: [] }, { ...basicCase, id: '3', totalComment: 0, comments: [] }, { ...basicCase, id: '4', totalComment: 0, comments: [] }, @@ -557,7 +564,14 @@ export const pushedCaseSnake = { export const casesSnake: CasesResponse = [ basicCaseSnake, - { ...pushedCaseSnake, id: '1', totalComment: 0, comments: [] }, + { + ...pushedCaseSnake, + id: '1', + totalComment: 0, + comments: [], + status: CaseStatuses['in-progress'], + severity: CaseSeverity.MEDIUM, + }, { ...pushedCaseSnake, updated_at: laterTime, id: '2', totalComment: 0, comments: [] }, { ...basicCaseSnake, id: '3', totalComment: 0, comments: [] }, { ...basicCaseSnake, id: '4', totalComment: 0, comments: [] }, diff --git a/x-pack/plugins/cases/server/client/cases/types.ts b/x-pack/plugins/cases/server/client/cases/types.ts index c14cc6621061..6d56fa28dca5 100644 --- a/x-pack/plugins/cases/server/client/cases/types.ts +++ b/x-pack/plugins/cases/server/client/cases/types.ts @@ -17,7 +17,7 @@ import { PushToServiceApiParamsITSM as ServiceNowITSMPushToServiceApiParams, PushToServiceApiParamsSIR as ServiceNowSIRPushToServiceApiParams, ServiceNowITSMIncident, -} from '@kbn/stack-connectors-plugin/server/connector_types/cases/servicenow/types'; +} from '@kbn/stack-connectors-plugin/server/connector_types/lib/servicenow/types'; import { UserProfile } from '@kbn/security-plugin/common'; import { CaseResponse, ConnectorMappingsAttributes } from '../../../common/api'; diff --git a/x-pack/plugins/enterprise_search/common/types/pipelines.ts b/x-pack/plugins/enterprise_search/common/types/pipelines.ts index 8652eb57f9b4..e9c564dd816a 100644 --- a/x-pack/plugins/enterprise_search/common/types/pipelines.ts +++ b/x-pack/plugins/enterprise_search/common/types/pipelines.ts @@ -25,3 +25,12 @@ export enum TrainedModelState { export interface MlInferencePipeline extends IngestPipeline { version?: number; } + +export interface MlInferenceHistoryItem { + doc_count: number; + pipeline: string; +} + +export interface MlInferenceHistoryResponse { + history: MlInferenceHistoryItem[]; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_history.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_history.ts new file mode 100644 index 000000000000..f1e825fffc0b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_history.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MlInferenceHistoryResponse } from '../../../../../common/types/pipelines'; +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface FetchMlInferencePipelineHistoryApiLogicArgs { + indexName: string; +} +export type FetchMlInferencePipelineHistoryApiLogicResponse = MlInferenceHistoryResponse; + +export const fetchMlInferencePipelineHistory = async ({ + indexName, +}: FetchMlInferencePipelineHistoryApiLogicArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/ml_inference/history`; + + return await HttpLogic.values.http.get(route); +}; + +export const FetchMlInferencePipelineHistoryApiLogic = createApiLogic( + ['fetch_ml_inference_pipeline_history_api_logic'], + fetchMlInferencePipelineHistory +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawl_requests_panel/crawl_requests_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawl_requests_panel/crawl_requests_panel.tsx index f5474d41aff8..75ff689819ff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawl_requests_panel/crawl_requests_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawl_requests_panel/crawl_requests_panel.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { useValues } from 'kea'; -import { EuiCode, EuiLink, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiCode, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DataPanel } from '../../../../../shared/data_panel/data_panel'; @@ -45,14 +45,6 @@ export const CrawlRequestsPanel: React.FC = () => { 'Requests originating from the crawler can be identified by the following User Agent. This is configured in your enterprise-search.yml file.', } )}{' '} - - {i18n.translate( - 'xpack.enterpriseSearch.crawler.crawlRequestsPanel.userAgentDocumentationLink', - { - defaultMessage: 'Learn more about Elastic crawler user agents', - } - )} - {data ? data.userAgent : ''} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_history.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_history.tsx new file mode 100644 index 000000000000..5f0de19f064e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_history.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiLink, + EuiSpacer, + EuiLoadingSpinner, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { MlInferenceHistoryItem } from '../../../../../../common/types/pipelines'; +import { DataPanel } from '../../../../shared/data_panel/data_panel'; + +import { InferenceHistoryLogic } from './inference_history_logic'; + +export const InferenceHistory: React.FC = () => { + const { indexName, isLoading, inferenceHistory } = useValues(InferenceHistoryLogic); + const { fetchIndexInferenceHistory } = useActions(InferenceHistoryLogic); + + useEffect(() => { + fetchIndexInferenceHistory({ indexName }); + }, [indexName]); + + const historyColumns: Array> = [ + { + dataType: 'string', + field: 'pipeline', + name: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.inferenceHistory.tableColumn.pipeline', + { defaultMessage: 'Inference pipeline' } + ), + }, + { + dataType: 'number', + field: 'doc_count', + name: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.inferenceHistory.tableColumn.docCount', + { defaultMessage: 'Approx. document count' } + ), + }, + ]; + return ( + <> + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.inferenceHistory.title', + { defaultMessage: 'Historical inference processors' } + )} + + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.inferenceHistory.subtitle', + { + defaultMessage: + 'The following inference processors were found in the _ingest.processors field of documents on this index.', + } + )} + footerDocLink={ + // TODO: insert real doc link + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.inferenceHistory.docLink', + { + defaultMessage: 'Learn more about inference history', + } + )} + + } + > + {isLoading ? ( + + ) : ( + + )} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_history_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_history_logic.ts new file mode 100644 index 000000000000..1a27f5dfd834 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_history_logic.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 { kea, MakeLogicType } from 'kea'; + +import { Status } from '../../../../../../common/types/api'; +import { MlInferenceHistoryItem } from '../../../../../../common/types/pipelines'; +import { Actions } from '../../../../shared/api_logic/create_api_logic'; +import { clearFlashMessages, flashAPIErrors } from '../../../../shared/flash_messages'; +import { + FetchMlInferencePipelineHistoryApiLogicArgs, + FetchMlInferencePipelineHistoryApiLogicResponse, + FetchMlInferencePipelineHistoryApiLogic, +} from '../../../api/pipelines/fetch_ml_inference_pipeline_history'; +import { IndexNameLogic } from '../index_name_logic'; + +interface InferenceHistoryActions { + fetchIndexInferenceHistory: Actions< + FetchMlInferencePipelineHistoryApiLogicArgs, + FetchMlInferencePipelineHistoryApiLogicResponse + >['makeRequest']; +} + +interface InferenceHistoryValues { + fetchIndexInferenceHistoryStatus: Status; + indexName: string; + inferenceHistory: MlInferenceHistoryItem[] | undefined; + inferenceHistoryData: FetchMlInferencePipelineHistoryApiLogicResponse | undefined; + isLoading: boolean; +} + +export const InferenceHistoryLogic = kea< + MakeLogicType +>({ + connect: { + actions: [ + FetchMlInferencePipelineHistoryApiLogic, + ['makeRequest as fetchIndexInferenceHistory', 'apiError as fetchIndexInferenceHistoryError'], + ], + values: [ + IndexNameLogic, + ['indexName'], + FetchMlInferencePipelineHistoryApiLogic, + ['data as inferenceHistoryData', 'status as fetchIndexInferenceHistoryStatus'], + ], + }, + listeners: () => ({ + fetchIndexInferenceHistory: () => clearFlashMessages(), + fetchIndexInferenceHistoryError: (error) => flashAPIErrors(error), + }), + path: ['enterprise_search', 'content', 'pipelines_inference_history'], + selectors: ({ selectors }) => ({ + inferenceHistory: [ + () => [selectors.inferenceHistoryData], + (inferenceHistoryData: FetchMlInferencePipelineHistoryApiLogicResponse | undefined) => + inferenceHistoryData?.history, + ], + isLoading: [ + () => [selectors.fetchIndexInferenceHistoryStatus], + (fetchIndexInferenceHistoryStatus: Status) => + fetchIndexInferenceHistoryStatus !== Status.SUCCESS && + fetchIndexInferenceHistoryStatus !== Status.ERROR, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx index 7bf1ef06e1e7..9202e6690408 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx @@ -37,7 +37,8 @@ import { PipelinesLogic } from './pipelines_logic'; export const IngestPipelinesCard: React.FC = () => { const { indexName } = useValues(IndexViewLogic); - const { canSetPipeline, index, pipelineState, showModal } = useValues(PipelinesLogic); + const { canSetPipeline, index, pipelineName, pipelineState, showModal } = + useValues(PipelinesLogic); const { closeModal, openModal, setPipelineState, savePipeline } = useActions(PipelinesLogic); const { makeRequest: fetchCustomPipeline } = useActions(FetchCustomPipelineApiLogic); const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic); @@ -61,7 +62,7 @@ export const IngestPipelinesCard: React.FC = () => { indexName={indexName} isGated={isGated} isLoading={false} - pipeline={pipelineState} + pipeline={{ ...pipelineState, name: pipelineName }} savePipeline={savePipeline} setPipeline={setPipelineState} showModal={showModal} @@ -84,7 +85,7 @@ export const IngestPipelinesCard: React.FC = () => { -

{pipelineState.name}

+

{pipelineName}

@@ -111,7 +112,7 @@ export const IngestPipelinesCard: React.FC = () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx index bd895dcf4570..99be659cbac3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx @@ -22,11 +22,29 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { docLinks } from '../../../../../shared/doc_links'; import { MLInferenceLogic } from './ml_inference_logic'; +const NoSourceFieldsError: React.FC = () => ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.sourceField.error.docLink', + { defaultMessage: 'Learn more about field mapping' } + )} + + ), + }} + /> +); + export const ConfigurePipeline: React.FC = () => { const { addInferencePipelineModal: { configuration }, @@ -39,6 +57,7 @@ export const ConfigurePipeline: React.FC = () => { const { destinationField, modelID, pipelineName, sourceField } = configuration; const models = supportedMLModels ?? []; const nameError = formErrors.pipelineName !== undefined && pipelineName.length > 0; + const emptySourceFields = (sourceFields?.length ?? 0) === 0; return ( <> @@ -143,6 +162,8 @@ export const ConfigurePipeline: React.FC = () => { defaultMessage: 'Source field', } )} + error={emptySourceFields && } + isInvalid={emptySourceFields} > HttpError; @@ -81,6 +83,7 @@ interface MLInferenceProcessorsValues { formErrors: AddInferencePipelineFormErrors; isLoading: boolean; isPipelineDataValid: boolean; + index: FetchIndexApiResponse; mappingData: typeof MappingsApiLogic.values.data; mappingStatus: Status; mlInferencePipeline?: MlInferencePipeline; @@ -118,6 +121,8 @@ export const MLInferenceLogic = kea< ], ], values: [ + FetchIndexApiLogic, + ['data as index'], MappingsApiLogic, ['data as mappingData', 'status as mappingStatus'], MLModelsApiLogic, @@ -126,14 +131,6 @@ export const MLInferenceLogic = kea< }, events: {}, listeners: ({ values, actions }) => ({ - createApiSuccess: () => { - KibanaLogic.values.navigateToUrl( - generateEncodedPath(SEARCH_INDEX_TAB_PATH, { - indexName: values.addInferencePipelineModal.indexName, - tabId: SearchIndexTabId.PIPELINES, - }) - ); - }, createPipeline: () => { const { addInferencePipelineModal: { configuration, indexName }, @@ -156,6 +153,7 @@ export const MLInferenceLogic = kea< actions.makeMappingRequest({ indexName }); }, }), + path: ['enterprise_search', 'content', 'pipelines_add_ml_inference_pipeline'], reducers: { addInferencePipelineModal: [ { @@ -223,10 +221,19 @@ export const MLInferenceLogic = kea< }, ], sourceFields: [ - () => [selectors.mappingStatus, selectors.mappingData], - (status: Status, mapping: IndicesGetMappingIndexMappingRecord) => { + () => [selectors.mappingStatus, selectors.mappingData, selectors.index], + ( + status: Status, + mapping: IndicesGetMappingIndexMappingRecord, + index: FetchIndexApiResponse + ) => { if (status !== Status.SUCCESS) return; - if (mapping?.mappings?.properties === undefined) return []; + if (mapping?.mappings?.properties === undefined) { + if (isConnectorIndex(index)) { + return DEFAULT_CONNECTOR_FIELDS; + } + return []; + } return Object.entries(mapping.mappings.properties) .reduce((fields, [key, value]) => { if (value.type === 'text' || value.type === 'keyword') { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_json_badges.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_json_badges.tsx new file mode 100644 index 000000000000..9a690ab437da --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_json_badges.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { EuiBadgeGroup, EuiBadge, EuiToolTip } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { DEFAULT_PIPELINE_NAME } from '../../../../../../common/constants'; + +import { isManagedPipeline } from '../../../../shared/pipelines/is_managed'; + +import { IndexPipelinesConfigurationsLogic } from './pipelines_json_configurations_logic'; + +const ManagedPipelineBadge: React.FC = () => ( + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.managed', + { defaultMessage: 'Managed' } + )} + + +); + +const UnmanagedPipelineBadge: React.FC = () => ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.ingestPipelines', + { defaultMessage: 'Ingest Pipelines' } + )} + + ), + }} + /> + } + > + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.unmanaged', + { defaultMessage: 'Unmanaged' } + )} + + +); + +const SharedPipelineBadge: React.FC = () => ( + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.shared', + { defaultMessage: 'Shared' } + )} + + +); + +const IndexPipelineBadge: React.FC = () => ( + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.indexSpecific', + { defaultMessage: 'Index specific' } + )} + + +); + +const MlInferenceBadge: React.FC = () => ( + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.mlInference', + { defaultMessage: 'ML Inference' } + )} + + +); + +export const PipelineJSONBadges: React.FC = () => { + const { + indexName, + selectedPipeline: pipeline, + selectedPipelineId: pipelineName, + } = useValues(IndexPipelinesConfigurationsLogic); + if (!pipeline) { + return <>; + } + const badges: JSX.Element[] = []; + if (isManagedPipeline(pipeline)) { + badges.push(); + } else { + badges.push(); + } + if (pipelineName === DEFAULT_PIPELINE_NAME) { + badges.push(); + } + if (pipelineName?.endsWith('@ml-inference')) { + badges.push(); + } else if (pipelineName?.includes(indexName)) { + badges.push(); + } + return {badges}; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx index 9cab24190a2d..e51479ddf0a8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx @@ -9,7 +9,15 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -17,23 +25,44 @@ import { DataPanel } from '../../../../shared/data_panel/data_panel'; import { docLinks } from '../../../../shared/doc_links'; import { isApiIndex } from '../../../utils/indices'; +import { InferenceHistory } from './inference_history'; import { IngestPipelinesCard } from './ingest_pipelines_card'; import { AddMLInferencePipelineButton } from './ml_inference/add_ml_inference_button'; import { AddMLInferencePipelineModal } from './ml_inference/add_ml_inference_pipeline_modal'; import { MlInferencePipelineProcessorsCard } from './ml_inference_pipeline_processors_card'; +import { PipelinesJSONConfigurations } from './pipelines_json_configurations'; import { PipelinesLogic } from './pipelines_logic'; export const SearchIndexPipelines: React.FC = () => { - const { - showAddMlInferencePipelineModal, - hasIndexIngestionPipeline, - index, - pipelineState: { name: pipelineName }, - } = useValues(PipelinesLogic); + const { showAddMlInferencePipelineModal, hasIndexIngestionPipeline, index, pipelineName } = + useValues(PipelinesLogic); const { closeAddMlInferencePipelineModal, openAddMlInferencePipelineModal } = useActions(PipelinesLogic); const apiIndex = isApiIndex(index); + const pipelinesTabs: EuiTabbedContentTab[] = [ + { + content: , + id: 'inference-history', + name: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.inferenceHistory', + { + defaultMessage: 'Inference history', + } + ), + }, + { + content: , + id: 'json-configurations', + name: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations', + { + defaultMessage: 'JSON configurations', + } + ), + }, + ]; + return ( <> @@ -82,8 +111,7 @@ export const SearchIndexPipelines: React.FC = () => { > - - + { 'xpack.enterpriseSearch.content.indices.pipelines.mlInferencePipelines.subtitleAPIindex', { defaultMessage: - "Inference pipelines will be run as processors from the Enterprise Search Ingest Pipeline. In order to use these pipeline on API-based indices you'll need to reference the {pipelineName} pipeline in your API requests.", + "Inference pipelines will be run as processors from the Enterprise Search Ingest Pipeline. In order to use these pipelines on API-based indices you'll need to reference the {pipelineName} pipeline in your API requests.", values: { pipelineName, }, @@ -134,6 +162,15 @@ export const SearchIndexPipelines: React.FC = () => { + + + + +
{showAddMlInferencePipelineModal && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations.tsx new file mode 100644 index 000000000000..3cba142347fc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations.tsx @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButtonEmpty, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiLink, + EuiNotificationBadge, + EuiSelect, + EuiSpacer, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { DataPanel } from '../../../../shared/data_panel/data_panel'; +import { docLinks } from '../../../../shared/doc_links'; +import { HttpLogic } from '../../../../shared/http'; +import { isManagedPipeline } from '../../../../shared/pipelines/is_managed'; + +import { PipelineJSONBadges } from './pipeline_json_badges'; +import { IndexPipelinesConfigurationsLogic } from './pipelines_json_configurations_logic'; + +export const PipelinesJSONConfigurations: React.FC = () => { + const { http } = useValues(HttpLogic); + const { pipelineNames, selectedPipeline, selectedPipelineId, selectedPipelineJSON } = useValues( + IndexPipelinesConfigurationsLogic + ); + const { selectPipeline } = useActions(IndexPipelinesConfigurationsLogic); + return ( + <> + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.title', + { defaultMessage: 'Pipeline configurations' } + )} + + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.subtitle', + { defaultMessage: 'View the JSON for your pipeline configurations on this index.' } + )} + footerDocLink={ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.ingestionPipelines.docLink', + { + defaultMessage: 'Learn more about how Enterprise Search uses ingest pipelines', + } + )} + + } + action={ + pipelineNames.length > 0 && ( + {pipelineNames.length} + ) + } + iconType="visVega" + > + + ({ text: name, value: name }))} + value={selectedPipelineId} + onChange={(e) => selectPipeline(e.target.value)} + /> + + + {selectedPipeline && ( + <> + + + + + + {isManagedPipeline(selectedPipeline) ? ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.action.view', + { + defaultMessage: 'View in Stack Management', + } + )} + + ) : ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.action.edit', + { + defaultMessage: 'Edit in Stack Management', + } + )} + + )} + + + + + {selectedPipelineJSON} + + + )} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations_logic.ts new file mode 100644 index 000000000000..4bdf541cc7c7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations_logic.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; + +import { Actions } from '../../../../shared/api_logic/create_api_logic'; +import { + FetchCustomPipelineApiLogicArgs, + FetchCustomPipelineApiLogicResponse, + FetchCustomPipelineApiLogic, +} from '../../../api/index/fetch_custom_pipeline_api_logic'; +import { IndexNameLogic } from '../index_name_logic'; + +interface IndexPipelinesConfigurationsActions { + fetchIndexPipelinesDataSuccess: Actions< + FetchCustomPipelineApiLogicArgs, + FetchCustomPipelineApiLogicResponse + >['apiSuccess']; + selectPipeline: (pipeline: string) => { pipeline: string }; +} + +interface IndexPipelinesConfigurationsValues { + indexName: string; + indexPipelinesData: FetchCustomPipelineApiLogicResponse; + pipelineNames: string[]; + pipelines: Record; + selectedPipeline: IngestPipeline | undefined; + selectedPipelineId: string; + selectedPipelineJSON: string; +} + +export const IndexPipelinesConfigurationsLogic = kea< + MakeLogicType +>({ + actions: { + selectPipeline: (pipeline: string) => ({ pipeline }), + }, + connect: { + actions: [FetchCustomPipelineApiLogic, ['apiSuccess as fetchIndexPipelinesDataSuccess']], + values: [ + IndexNameLogic, + ['indexName'], + FetchCustomPipelineApiLogic, + ['data as indexPipelinesData'], + ], + }, + events: ({ actions, values }) => ({ + afterMount: () => { + if (!values.indexPipelinesData || values.indexPipelinesData.length === 0) { + return; + } + const pipelineNames = Object.keys(values.indexPipelinesData).sort(); + const defaultPipeline = pipelineNames.includes(values.indexName) + ? values.indexName + : pipelineNames[0]; + actions.selectPipeline(defaultPipeline); + }, + }), + listeners: ({ actions, values }) => ({ + fetchIndexPipelinesDataSuccess: (pipelines) => { + const names = Object.keys(pipelines ?? {}).sort(); + if (names.length > 0 && values.selectedPipelineId.length === 0) { + const defaultPipeline = names.includes(values.indexName) ? values.indexName : names[0]; + actions.selectPipeline(defaultPipeline); + } + }, + }), + path: ['enterprise_search', 'content', 'pipelines_json_configurations'], + reducers: () => ({ + selectedPipelineId: [ + '', + { + selectPipeline: (_, { pipeline }) => pipeline, + }, + ], + }), + selectors: ({ selectors }) => ({ + pipelines: [ + () => [selectors.indexPipelinesData], + (indexPipelines: FetchCustomPipelineApiLogicResponse) => { + return indexPipelines ?? {}; + }, + ], + pipelineNames: [ + () => [selectors.pipelines], + (pipelines: Record) => { + return Object.keys(pipelines).sort(); + }, + ], + selectedPipeline: [ + () => [selectors.selectedPipelineId, selectors.pipelines], + (selectedPipelineId: string, pipelines: Record) => { + if (pipelines.hasOwnProperty(selectedPipelineId)) { + return pipelines[selectedPipelineId]; + } + return undefined; + }, + ], + selectedPipelineJSON: [ + () => [selectors.selectedPipeline], + (selectedPipeline: IngestPipeline | undefined) => { + if (selectedPipeline) { + return JSON.stringify(selectedPipeline, null, 2); + } + return ''; + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts index 7dc3a221cc57..ff3b779d61e2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts @@ -4,11 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { LogicMounter, mockFlashMessageHelpers } from '../../../../__mocks__/kea_logic'; -import { connectorIndex } from '../../../__mocks__/view_index.mock'; +import { apiIndex, connectorIndex } from '../../../__mocks__/view_index.mock'; + +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { UpdatePipelineApiLogic } from '../../../api/connector/update_pipeline_api_logic'; +import { FetchCustomPipelineApiLogic } from '../../../api/index/fetch_custom_pipeline_api_logic'; import { FetchIndexApiLogic } from '../../../api/index/fetch_index_api_logic'; import { PipelinesLogic } from './pipelines_logic'; @@ -23,20 +25,24 @@ const DEFAULT_PIPELINE_VALUES = { const DEFAULT_VALUES = { canSetPipeline: true, canUseMlInferencePipeline: false, + customPipelineData: undefined, defaultPipelineValues: DEFAULT_PIPELINE_VALUES, defaultPipelineValuesData: undefined, + hasIndexIngestionPipeline: false, index: undefined, + indexName: '', mlInferencePipelineProcessors: undefined, + pipelineName: DEFAULT_PIPELINE_VALUES.name, pipelineState: DEFAULT_PIPELINE_VALUES, - showModal: false, showAddMlInferencePipelineModal: false, - hasIndexIngestionPipeline: false, + showModal: false, }; describe('PipelinesLogic', () => { const { mount } = new LogicMounter(PipelinesLogic); const { mount: mountFetchIndexApiLogic } = new LogicMounter(FetchIndexApiLogic); const { mount: mountUpdatePipelineLogic } = new LogicMounter(UpdatePipelineApiLogic); + const { mount: mountFetchCustomPipelineApiLogic } = new LogicMounter(FetchCustomPipelineApiLogic); const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; const newPipeline = { @@ -48,6 +54,7 @@ describe('PipelinesLogic', () => { beforeEach(() => { jest.clearAllMocks(); mountFetchIndexApiLogic(); + mountFetchCustomPipelineApiLogic(); mountUpdatePipelineLogic(); mount(); }); @@ -69,6 +76,7 @@ describe('PipelinesLogic', () => { ...connectorIndex, connector: { ...connectorIndex.connector }, }, + indexName: 'connector', }); expect(flashSuccessToast).toHaveBeenCalled(); expect(PipelinesLogic.actions.fetchIndexApiSuccess).toHaveBeenCalledWith({ @@ -86,8 +94,9 @@ describe('PipelinesLogic', () => { }); expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, - pipelineState: { ...DEFAULT_PIPELINE_VALUES, name: 'new_pipeline_name' }, hasIndexIngestionPipeline: true, + pipelineName: 'new_pipeline_name', + pipelineState: { ...DEFAULT_PIPELINE_VALUES, name: 'new_pipeline_name' }, }); }); describe('makeRequest', () => { @@ -152,12 +161,14 @@ describe('PipelinesLogic', () => { expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, canUseMlInferencePipeline: true, + hasIndexIngestionPipeline: true, index: { ...connectorIndex, connector: { ...connectorIndex.connector, pipeline: newPipeline }, }, + indexName: 'connector', + pipelineName: 'new_pipeline_name', pipelineState: newPipeline, - hasIndexIngestionPipeline: true, }); }); it('should not set configState if modal is open', () => { @@ -172,6 +183,7 @@ describe('PipelinesLogic', () => { ...connectorIndex, connector: { ...connectorIndex.connector, pipeline: newPipeline }, }, + indexName: 'connector', showModal: true, }); }); @@ -187,5 +199,41 @@ describe('PipelinesLogic', () => { }); }); }); + describe('fetchCustomPipelineSuccess', () => { + it('should support api indices with custom ingest pipelines', () => { + PipelinesLogic.actions.fetchIndexApiSuccess({ + ...apiIndex, + }); + const indexName = apiIndex.name; + const indexPipelines: Record = { + [indexName]: { + processors: [], + version: 1, + }, + [`${indexName}@custom`]: { + processors: [], + version: 1, + }, + [`${indexName}@ml-inference`]: { + processors: [], + version: 1, + }, + }; + PipelinesLogic.actions.fetchCustomPipelineSuccess(indexPipelines); + + expect(PipelinesLogic.values).toEqual({ + ...DEFAULT_VALUES, + customPipelineData: indexPipelines, + index: { + ...apiIndex, + }, + indexName, + pipelineName: indexName, + canSetPipeline: false, + hasIndexIngestionPipeline: true, + canUseMlInferencePipeline: true, + }); + }); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts index 99d241507dd2..dca18863cde0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts @@ -7,6 +7,7 @@ import { kea, MakeLogicType } from 'kea'; +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { i18n } from '@kbn/i18n'; import { DEFAULT_PIPELINE_VALUES } from '../../../../../../common/constants'; @@ -89,6 +90,10 @@ type PipelinesActions = Pick< FetchCustomPipelineApiLogicArgs, FetchCustomPipelineApiLogicResponse >['makeRequest']; + fetchCustomPipelineSuccess: Actions< + FetchCustomPipelineApiLogicArgs, + FetchCustomPipelineApiLogicResponse + >['apiSuccess']; fetchDefaultPipeline: Actions['makeRequest']; fetchDefaultPipelineSuccess: Actions['apiSuccess']; fetchIndexApiSuccess: Actions['apiSuccess']; @@ -105,11 +110,14 @@ type PipelinesActions = Pick< interface PipelinesValues { canSetPipeline: boolean; canUseMlInferencePipeline: boolean; + customPipelineData: Record; defaultPipelineValues: IngestPipelineParams; defaultPipelineValuesData: IngestPipelineParams | null; hasIndexIngestionPipeline: boolean; index: FetchIndexApiResponse; + indexName: string; mlInferencePipelineProcessors: InferencePipeline[]; + pipelineName: string; pipelineState: IngestPipelineParams; showAddMlInferencePipelineModal: boolean; showModal: boolean; @@ -139,7 +147,7 @@ export const PipelinesLogic = kea { + // Re-fetch processors to ensure we display newly added ml processor actions.fetchMlInferenceProcessors({ indexName: values.index.name }); + // Needed to ensure correct JSON is available in the JSON configurations tab + actions.fetchCustomPipeline({ indexName: values.index.name }); }, deleteMlPipelineError: (error) => flashAPIErrors(error), deleteMlPipelineSuccess: (value) => { @@ -229,7 +242,10 @@ export const PipelinesLogic = kea { if (!values.showModal) { @@ -290,26 +306,31 @@ export const PipelinesLogic = kea [selectors.index], (index: ElasticsearchIndexWithIngestion) => !isApiIndex(index), ], + canUseMlInferencePipeline: [ + () => [selectors.hasIndexIngestionPipeline, selectors.pipelineState, selectors.index], + ( + hasIndexIngestionPipeline: boolean, + pipelineState: IngestPipelineParams, + index: ElasticsearchIndexWithIngestion + ) => hasIndexIngestionPipeline && (pipelineState.run_ml_inference || isApiIndex(index)), + ], defaultPipelineValues: [ () => [selectors.defaultPipelineValuesData], (pipeline: IngestPipelineParams | null) => pipeline ?? DEFAULT_PIPELINE_VALUES, ], hasIndexIngestionPipeline: [ - () => [selectors.pipelineState, selectors.defaultPipelineValues], - (pipelineState: IngestPipelineParams, defaultPipelineValues: IngestPipelineParams) => - pipelineState.name !== defaultPipelineValues.name, + () => [selectors.pipelineName, selectors.defaultPipelineValues], + (pipelineName: string, defaultPipelineValues: IngestPipelineParams) => + pipelineName !== defaultPipelineValues.name, ], - canUseMlInferencePipeline: [ - () => [ - selectors.canSetPipeline, - selectors.hasIndexIngestionPipeline, - selectors.pipelineState, - ], - ( - canSetPipeline: boolean, - hasIndexIngestionPipeline: boolean, - pipelineState: IngestPipelineParams - ) => canSetPipeline && hasIndexIngestionPipeline && pipelineState.run_ml_inference, + indexName: [ + () => [selectors.index], + (index?: ElasticsearchIndexWithIngestion) => index?.name ?? '', + ], + pipelineName: [ + () => [selectors.pipelineState, selectors.customPipelineData, selectors.indexName], + (pipelineState, customPipelineData, indexName) => + customPipelineData && customPipelineData[indexName] ? indexName : pipelineState.name, ], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx index 6dbb44d658b8..c7b5ebaf6661 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx @@ -22,6 +22,7 @@ export const DeleteIndexModal: React.FC = () => { deleteModalIndexName: indexName, deleteModalIngestionMethod: ingestionMethod, isDeleteModalVisible, + isDeleteLoading, } = useValues(IndicesLogic); return isDeleteModalVisible ? ( { onConfirm={() => { deleteIndex({ indexName }); }} - cancelButtonText={i18n.translate( - 'xpack.enterpriseSearch.content.searchIndices.deleteModal.cancelButton.title', - { - defaultMessage: 'Cancel', - } - )} + cancelButtonText={ + isDeleteLoading + ? i18n.translate( + 'xpack.enterpriseSearch.content.searchIndices.deleteModal.closeButton.title', + { + defaultMessage: 'Close', + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.searchIndices.deleteModal.cancelButton.title', + { + defaultMessage: 'Cancel', + } + ) + } confirmButtonText={i18n.translate( 'xpack.enterpriseSearch.content.searchIndices.deleteModal.confirmButton.title', { @@ -49,6 +59,7 @@ export const DeleteIndexModal: React.FC = () => { )} defaultFocusedButton="confirm" buttonColor="danger" + isLoading={isDeleteLoading} >

{i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts index a636199dd1e4..ff761b17e738 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts @@ -31,8 +31,10 @@ const DEFAULT_VALUES = { deleteModalIndex: null, deleteModalIndexName: '', deleteModalIngestionMethod: IngestionMethod.API, + deleteStatus: Status.IDLE, hasNoIndices: false, indices: [], + isDeleteLoading: false, isDeleteModalVisible: false, isFirstRequest: true, isLoading: true, @@ -255,6 +257,36 @@ describe('IndicesLogic', () => { }); }); }); + describe('deleteRequest', () => { + it('should update isDeleteLoading to true on deleteIndex', () => { + IndicesLogic.actions.deleteIndex({ indexName: 'to-delete' }); + expect(IndicesLogic.values).toEqual({ + ...DEFAULT_VALUES, + deleteStatus: Status.LOADING, + isDeleteLoading: true, + }); + }); + it('should update isDeleteLoading to to false on apiError', () => { + IndicesLogic.actions.deleteIndex({ indexName: 'to-delete' }); + IndicesLogic.actions.deleteError({} as HttpError); + + expect(IndicesLogic.values).toEqual({ + ...DEFAULT_VALUES, + deleteStatus: Status.ERROR, + isDeleteLoading: false, + }); + }); + it('should update isDeleteLoading to to false on apiSuccess', () => { + IndicesLogic.actions.deleteIndex({ indexName: 'to-delete' }); + IndicesLogic.actions.deleteSuccess(); + + expect(IndicesLogic.values).toEqual({ + ...DEFAULT_VALUES, + deleteStatus: Status.SUCCESS, + isDeleteLoading: false, + }); + }); + }); }); describe('listeners', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts index 5b90b26dba00..a771a29a3c0d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts @@ -72,8 +72,10 @@ export interface IndicesValues { deleteModalIndex: ElasticsearchViewIndex | null; deleteModalIndexName: string; deleteModalIngestionMethod: IngestionMethod; + deleteStatus: typeof DeleteIndexApiLogic.values.status; hasNoIndices: boolean; indices: ElasticsearchViewIndex[]; + isDeleteLoading: boolean; isDeleteModalVisible: boolean; isFirstRequest: boolean; isLoading: boolean; @@ -101,7 +103,12 @@ export const IndicesLogic = kea>({ DeleteIndexApiLogic, ['apiError as deleteError', 'apiSuccess as deleteSuccess', 'makeRequest as deleteIndex'], ], - values: [FetchIndicesAPILogic, ['data', 'status']], + values: [ + FetchIndicesAPILogic, + ['data', 'status'], + DeleteIndexApiLogic, + ['status as deleteStatus'], + ], }, listeners: ({ actions, values }) => ({ apiError: (e) => flashAPIErrors(e), @@ -181,6 +188,10 @@ export const IndicesLogic = kea>({ () => [selectors.data], (data) => (data?.indices ? data.indices.map(indexToViewIndex) : []), ], + isDeleteLoading: [ + () => [selectors.deleteStatus], + (status: IndicesValues['deleteStatus']) => [Status.LOADING].includes(status), + ], isLoading: [ () => [selectors.status, selectors.isFirstRequest], (status, isFirstRequest) => [Status.LOADING, Status.IDLE].includes(status) && isFirstRequest, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/pipelines/is_managed.ts b/x-pack/plugins/enterprise_search/public/applications/shared/pipelines/is_managed.ts new file mode 100644 index 000000000000..30cf5ac145c8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/pipelines/is_managed.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; + +interface IngestPipelineWithMetadata extends IngestPipeline { + _meta: { + managed?: boolean; + managed_by?: string; + }; +} + +const isIngestPipelineWithMetadata = ( + pipeline: IngestPipeline +): pipeline is IngestPipelineWithMetadata => { + return pipeline.hasOwnProperty('_meta'); +}; + +export const isManagedPipeline = (pipeline: IngestPipeline): boolean => { + if (isIngestPipelineWithMetadata(pipeline)) { + return Boolean(pipeline._meta.managed); + } + return false; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts index f9756119b336..2166c840ee78 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts @@ -48,7 +48,7 @@ describe('EnterpriseSearchRequestHandler', () => { meta: { page: { total_results: 1 } }, }; - EnterpriseSearchAPI.mockReturn(responseBody); + enterpriseSearchAPI.mockReturn(responseBody); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/as/credentials/collection', @@ -61,7 +61,7 @@ describe('EnterpriseSearchRequestHandler', () => { }, }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith( + enterpriseSearchAPI.shouldHaveBeenCalledWith( 'http://localhost:3002/as/credentials/collection?type=indexed&pageIndex=1', { method: 'GET' } ); @@ -80,12 +80,12 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler, { route: { method: 'POST' } }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', { + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', { method: 'POST', }); await makeAPICall(requestHandler, { route: { method: 'DELETE' } }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', { + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', { method: 'DELETE', }); }); @@ -96,7 +96,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler, { body: { bodacious: true } }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', { + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', { body: '{"bodacious":true}', }); }); @@ -107,7 +107,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler, { body: Buffer.from('{"bodacious":true}') }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', { + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', { body: '{"bodacious":true}', }); }); @@ -118,7 +118,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler, { query: { someQuery: false } }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith( + enterpriseSearchAPI.shouldHaveBeenCalledWith( 'http://localhost:3002/api/example?someQuery=false' ); }); @@ -130,7 +130,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler, { query: { someQuery: false } }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith( + enterpriseSearchAPI.shouldHaveBeenCalledWith( 'http://localhost:3002/api/example?someQuery=true' ); }); @@ -141,7 +141,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler, { query: { 'page[current]': 1 } }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith( + enterpriseSearchAPI.shouldHaveBeenCalledWith( 'http://localhost:3002/api/example?page%5Bcurrent%5D=1' ); }); @@ -153,7 +153,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler, { params: { example: 'hello', id: 'world' } }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith( + enterpriseSearchAPI.shouldHaveBeenCalledWith( 'http://localhost:3002/api/examples/hello/some/world' ); }); @@ -164,7 +164,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler, { params: { example: 'hello#@/$%^/&[]{}/";world' } }); - EnterpriseSearchAPI.shouldHaveBeenCalledWith( + enterpriseSearchAPI.shouldHaveBeenCalledWith( 'http://localhost:3002/api/examples/hello%23%40%2F%24%25%5E%2F%26%5B%5D%7B%7D%2F%22%3Bworld' ); }); @@ -173,14 +173,14 @@ describe('EnterpriseSearchRequestHandler', () => { describe('response passing', () => { it('returns the response status code from Enterprise Search', async () => { - EnterpriseSearchAPI.mockReturn({}, { status: 201 }); + enterpriseSearchAPI.mockReturn({}, { status: 201 }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/example', }); await makeAPICall(requestHandler); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example'); + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example'); expect(responseMock.custom).toHaveBeenCalledWith({ body: {}, statusCode: 201, @@ -196,7 +196,7 @@ describe('EnterpriseSearchRequestHandler', () => { regular: 'data', }; - EnterpriseSearchAPI.mockReturn(jsonWithSessionData, { headers: JSON_HEADER }); + enterpriseSearchAPI.mockReturn(jsonWithSessionData, { headers: JSON_HEADER }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/prep' }); await makeAPICall(requestHandler); @@ -212,7 +212,7 @@ describe('EnterpriseSearchRequestHandler', () => { it('passes back the response body as-is if hasJsonResponse is false', async () => { const mockFile = new File(['mockFile'], 'mockFile.json'); - EnterpriseSearchAPI.mockReturn(mockFile); + enterpriseSearchAPI.mockReturn(mockFile); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/file', @@ -220,7 +220,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/file'); + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/file'); expect(responseMock.custom).toHaveBeenCalledWith({ body: expect.any(Buffer), // Unfortunately Response() buffers the body so we can't actually inspect/equality assert on it statusCode: 200, @@ -233,13 +233,13 @@ describe('EnterpriseSearchRequestHandler', () => { describe('error responses', () => { describe('handleClientError()', () => { afterEach(() => { - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/4xx'); + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/4xx'); expect(mockLogger.error).not.toHaveBeenCalled(); }); it('passes back json.error', async () => { const error = 'some error message'; - EnterpriseSearchAPI.mockReturn({ error }, { status: 404, headers: JSON_HEADER }); + enterpriseSearchAPI.mockReturn({ error }, { status: 404, headers: JSON_HEADER }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' }); await makeAPICall(requestHandler); @@ -256,7 +256,7 @@ describe('EnterpriseSearchRequestHandler', () => { it('passes back json.errors', async () => { const errors = ['one', 'two', 'three']; - EnterpriseSearchAPI.mockReturn({ errors }, { status: 400, headers: JSON_HEADER }); + enterpriseSearchAPI.mockReturn({ errors }, { status: 400, headers: JSON_HEADER }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' }); await makeAPICall(requestHandler); @@ -272,7 +272,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); it('handles empty json', async () => { - EnterpriseSearchAPI.mockReturn({}, { status: 400, headers: JSON_HEADER }); + enterpriseSearchAPI.mockReturn({}, { status: 400, headers: JSON_HEADER }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' }); await makeAPICall(requestHandler); @@ -288,7 +288,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); it('handles invalid json', async () => { - EnterpriseSearchAPI.mockReturn('invalid' as any, { status: 400, headers: JSON_HEADER }); + enterpriseSearchAPI.mockReturn('invalid' as any, { status: 400, headers: JSON_HEADER }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' }); await makeAPICall(requestHandler); @@ -304,7 +304,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); it('handles blank bodies', async () => { - EnterpriseSearchAPI.mockReturn(undefined as any, { status: 404 }); + enterpriseSearchAPI.mockReturn(undefined as any, { status: 404 }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' }); await makeAPICall(requestHandler); @@ -321,11 +321,11 @@ describe('EnterpriseSearchRequestHandler', () => { }); it('handleServerError()', async () => { - EnterpriseSearchAPI.mockReturn('something crashed!' as any, { status: 500 }); + enterpriseSearchAPI.mockReturn('something crashed!' as any, { status: 500 }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/5xx' }); await makeAPICall(requestHandler); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/5xx'); + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/5xx'); expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 502, @@ -338,14 +338,14 @@ describe('EnterpriseSearchRequestHandler', () => { }); it('handleReadOnlyModeError()', async () => { - EnterpriseSearchAPI.mockReturn( + enterpriseSearchAPI.mockReturn( { errors: ['Read only mode'] }, { status: 503, headers: { ...JSON_HEADER, [READ_ONLY_MODE_HEADER]: 'true' } } ); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/503' }); await makeAPICall(requestHandler); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/503'); + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/503'); expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 503, @@ -358,14 +358,14 @@ describe('EnterpriseSearchRequestHandler', () => { }); it('handleInvalidDataError()', async () => { - EnterpriseSearchAPI.mockReturn({ results: false }); + enterpriseSearchAPI.mockReturn({ results: false }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/invalid', hasValidData: (body?: any) => Array.isArray(body?.results), }); await makeAPICall(requestHandler); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/invalid'); + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/invalid'); expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 502, @@ -378,11 +378,11 @@ describe('EnterpriseSearchRequestHandler', () => { }); it('handleConnectionError()', async () => { - EnterpriseSearchAPI.mockReturnError(); + enterpriseSearchAPI.mockReturnError(); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/failed' }); await makeAPICall(requestHandler); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/failed'); + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/failed'); expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 502, @@ -399,7 +399,7 @@ describe('EnterpriseSearchRequestHandler', () => { }); await makeAPICall(requestHandler); - EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/unauthenticated'); + enterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/unauthenticated'); expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 502, body: 'Cannot authenticate Enterprise Search user', @@ -409,21 +409,21 @@ describe('EnterpriseSearchRequestHandler', () => { }); it('errors when receiving a 401 response', async () => { - EnterpriseSearchAPI.mockReturn({}, { status: 401 }); + enterpriseSearchAPI.mockReturn({}, { status: 401 }); }); it('errors when redirected to /login', async () => { - EnterpriseSearchAPI.mockReturn({}, { url: 'http://localhost:3002/login' }); + enterpriseSearchAPI.mockReturn({}, { url: 'http://localhost:3002/login' }); }); it('errors when redirected to /ent/select', async () => { - EnterpriseSearchAPI.mockReturn({}, { url: 'http://localhost:3002/ent/select' }); + enterpriseSearchAPI.mockReturn({}, { url: 'http://localhost:3002/ent/select' }); }); }); }); it('setResponseHeaders', async () => { - EnterpriseSearchAPI.mockReturn('anything' as any, { + enterpriseSearchAPI.mockReturn('anything' as any, { headers: { [READ_ONLY_MODE_HEADER]: 'true' }, }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/' }); @@ -449,7 +449,7 @@ describe('EnterpriseSearchRequestHandler', () => { regular: 'data', }; - EnterpriseSearchAPI.mockReturn(sessionDataBody, { headers: JSON_HEADER }); + enterpriseSearchAPI.mockReturn(sessionDataBody, { headers: JSON_HEADER }); const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/' }); await makeAPICall(requestHandler); @@ -477,7 +477,7 @@ const makeAPICall = (handler: Function, params = {}) => { return handler(null, request, responseMock); }; -const EnterpriseSearchAPI = { +const enterpriseSearchAPI = { shouldHaveBeenCalledWith(expectedUrl: string, expectedParams = {}) { expect(fetchMock).toHaveBeenCalledWith(expectedUrl, { headers: { Authorization: 'Basic 123', ...JSON_HEADER }, diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts new file mode 100644 index 000000000000..61d11b50ae48 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + AggregationsMultiBucketAggregateBase, + AggregationsStringRareTermsBucketKeys, + SearchResponse, +} from '@elastic/elasticsearch/lib/api/types'; +import { ElasticsearchClient } from '@kbn/core/server'; + +import { MlInferenceHistoryResponse } from '../../../common/types/pipelines'; + +import { fetchMlInferencePipelineHistory } from './fetch_ml_inference_pipeline_history'; + +const DEFAULT_RESPONSE: SearchResponse = { + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + hits: { + hits: [], + max_score: null, + total: { + relation: 'eq' as 'eq', + value: 10, + }, + }, + timed_out: false, + took: 1, +}; +type HistorySearchResponse = SearchResponse< + unknown, + { + inference_processors: AggregationsMultiBucketAggregateBase; + } +>; +const emptyMockResponse: HistorySearchResponse = { + ...DEFAULT_RESPONSE, + aggregations: { + inference_processors: { + buckets: [], + }, + }, +}; +const listMockResponse: HistorySearchResponse = { + ...DEFAULT_RESPONSE, + aggregations: { + inference_processors: { + buckets: [ + { + doc_count: 20, + key: 'ml-inference-test-001', + }, + { + doc_count: 33, + key: 'ml-inference-test-002', + }, + ], + }, + }, +}; +const objectMockResponse: HistorySearchResponse = { + ...DEFAULT_RESPONSE, + aggregations: { + inference_processors: { + buckets: { + '11111': { + doc_count: 20, + key: 'ml-inference-test-001', + }, + '22222': { + doc_count: 33, + key: 'ml-inference-test-002', + }, + }, + }, + }, +}; + +const expectedMockResults: MlInferenceHistoryResponse = { + history: [ + { + doc_count: 20, + pipeline: 'ml-inference-test-001', + }, + { + doc_count: 33, + pipeline: 'ml-inference-test-002', + }, + ], +}; + +describe('fetchMlInferencePipelineHistory', () => { + const mockClient = { + search: jest.fn(), + }; + const client = mockClient as unknown as ElasticsearchClient; + const indexName = 'unit-test-index'; + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should query ingest pipelines from documents', async () => { + mockClient.search.mockResolvedValue(emptyMockResponse); + + await fetchMlInferencePipelineHistory(client, indexName); + expect(mockClient.search).toHaveBeenCalledWith({ + aggs: { + inference_processors: { + terms: { + field: '_ingest.processors.pipeline.enum', + size: 100, + }, + }, + }, + index: indexName, + size: 0, + }); + }); + it('should return empty history when no results found', async () => { + mockClient.search.mockResolvedValue(emptyMockResponse); + + const response = await fetchMlInferencePipelineHistory(client, indexName); + expect(response).toEqual({ history: [] }); + }); + it('should return empty history when no aggregations returned', async () => { + mockClient.search.mockResolvedValue(DEFAULT_RESPONSE); + + const response = await fetchMlInferencePipelineHistory(client, indexName); + expect(response).toEqual({ history: [] }); + }); + it('should return history with aggregated list', async () => { + mockClient.search.mockResolvedValue(listMockResponse); + + const response = await fetchMlInferencePipelineHistory(client, indexName); + expect(response).toEqual(expectedMockResults); + }); + it('should return history with aggregated object', async () => { + mockClient.search.mockResolvedValue(objectMockResponse); + + const response = await fetchMlInferencePipelineHistory(client, indexName); + expect(response).toEqual(expectedMockResults); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts new file mode 100644 index 000000000000..70cbe687590c --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AggregationsMultiBucketAggregateBase, + AggregationsStringRareTermsBucketKeys, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { MlInferenceHistoryResponse } from '../../../common/types/pipelines'; + +export const fetchMlInferencePipelineHistory = async ( + client: ElasticsearchClient, + index: string +): Promise => { + const ingestPipelineProcessorsResult = await client.search< + unknown, + { + inference_processors: AggregationsMultiBucketAggregateBase; + } + >({ + aggs: { + inference_processors: { + terms: { + field: '_ingest.processors.pipeline.enum', + size: 100, + }, + }, + }, + index, + size: 0, + }); + + const processorBuckets = + ingestPipelineProcessorsResult.aggregations?.inference_processors.buckets; + if (!processorBuckets) { + return { history: [] }; + } + const bucketsList = Array.isArray(processorBuckets) + ? processorBuckets + : Object.values(processorBuckets); + return { + history: bucketsList.map((bucket) => ({ + doc_count: bucket.doc_count, + pipeline: bucket.key, + })), + }; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts new file mode 100644 index 000000000000..d9939afedaa5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { getMlInferenceErrors } from './get_inference_errors'; + +describe('getMlInferenceErrors', () => { + const indexName = 'my-index'; + + const mockClient = { + search: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch aggregations and transform them', async () => { + mockClient.search.mockImplementation(() => + Promise.resolve({ + aggregations: { + errors: { + buckets: [ + { + key: 'Error message 1', + doc_count: 100, + max_error_timestamp: { + value: 1664977836100, + value_as_string: '2022-10-05T13:50:36.100Z', + }, + }, + { + key: 'Error message 2', + doc_count: 200, + max_error_timestamp: { + value: 1664977836200, + value_as_string: '2022-10-05T13:50:36.200Z', + }, + }, + ], + }, + }, + }) + ); + + const actualResult = await getMlInferenceErrors( + indexName, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).toEqual([ + { + message: 'Error message 1', + doc_count: 100, + timestamp: '2022-10-05T13:50:36.100Z', + }, + { + message: 'Error message 2', + doc_count: 200, + timestamp: '2022-10-05T13:50:36.200Z', + }, + ]); + expect(mockClient.search).toHaveBeenCalledTimes(1); + }); + + it('should return an empty array if there are no aggregates', async () => { + mockClient.search.mockImplementation(() => + Promise.resolve({ + aggregations: { + errors: [], + }, + }) + ); + + const actualResult = await getMlInferenceErrors( + indexName, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).toEqual([]); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts b/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts new file mode 100644 index 000000000000..1ced837f42f2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AggregationsMultiBucketAggregateBase, + AggregationsStringRareTermsBucketKeys, +} from '@elastic/elasticsearch/lib/api/types'; + +import { ElasticsearchClient } from '@kbn/core/server'; + +export interface MlInferenceError { + message: string; + doc_count: number; + timestamp: string | undefined; // Date string +} + +export interface ErrorAggregationBucket extends AggregationsStringRareTermsBucketKeys { + max_error_timestamp: { + value: number | null; + value_as_string?: string; + }; +} + +/** + * Fetches an aggregate of distinct ML inference errors from the target index, along with the most + * recent error's timestamp and affected document count for each bucket. + * @param indexName the index to get the errors from. + * @param esClient the Elasticsearch Client to use to fetch the errors. + */ +export const getMlInferenceErrors = async ( + indexName: string, + esClient: ElasticsearchClient +): Promise => { + const searchResult = await esClient.search< + unknown, + { + errors: AggregationsMultiBucketAggregateBase; + } + >({ + index: indexName, + body: { + aggs: { + errors: { + terms: { + field: '_ingest.inference_errors.message.enum', + order: { + max_error_timestamp: 'desc', + }, + size: 20, + }, + aggs: { + max_error_timestamp: { + max: { + field: '_ingest.inference_errors.timestamp', + }, + }, + }, + }, + }, + size: 0, + }, + }); + + const errorBuckets = searchResult.aggregations?.errors.buckets; + if (!errorBuckets) { + return []; + } + + // Buckets are either in an array or in a Record, we transform them to an array + const buckets = Array.isArray(errorBuckets) ? errorBuckets : Object.values(errorBuckets); + + return buckets.map((bucket) => ({ + message: bucket.key, + doc_count: bucket.doc_count, + timestamp: bucket.max_error_timestamp?.value_as_string, + })); +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts index 183e27a765c2..3d54396a7d74 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts @@ -30,11 +30,11 @@ describe('createIndexPipelineDefinitions util function', () => { jest.clearAllMocks(); }); - it('should create the pipelines', () => { + it('should create the pipelines', async () => { mockClient.ingest.putPipeline.mockImplementation(() => Promise.resolve({ acknowledged: true })); - expect( + await expect( createIndexPipelineDefinitions(indexName, mockClient as unknown as ElasticsearchClient) - ).toEqual(expectedResult); + ).resolves.toEqual(expectedResult); expect(mockClient.ingest.putPipeline).toHaveBeenCalledTimes(3); }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts index f32590fb516c..4eba6dc5b0c8 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts @@ -27,24 +27,24 @@ export interface CreatedPipelines { * @param indexName the index for which the pipelines should be created. * @param esClient the Elasticsearch Client with which to create the pipelines. */ -export const createIndexPipelineDefinitions = ( +export const createIndexPipelineDefinitions = async ( indexName: string, esClient: ElasticsearchClient -): CreatedPipelines => { +): Promise => { // TODO: add back descriptions (see: https://github.com/elastic/elasticsearch-specification/issues/1827) - esClient.ingest.putPipeline({ + await esClient.ingest.putPipeline({ description: `Enterprise Search Machine Learning Inference pipeline for the '${indexName}' index`, id: getInferencePipelineNameFromIndexName(indexName), processors: [], version: 1, }); - esClient.ingest.putPipeline({ + await esClient.ingest.putPipeline({ description: `Enterprise Search customizable ingest pipeline for the '${indexName}' index`, id: `${indexName}@custom`, processors: [], version: 1, }); - esClient.ingest.putPipeline({ + await esClient.ingest.putPipeline({ _meta: { managed: true, managed_by: 'Enterprise Search', diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/get_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_pipeline.ts new file mode 100644 index 000000000000..a02b4cdd8b19 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_pipeline.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 { IngestGetPipelineResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { IScopedClusterClient } from '@kbn/core/server'; + +export const getPipeline = async ( + pipelineName: string, + client: IScopedClusterClient +): Promise => { + try { + const pipelinesResponse = await client.asCurrentUser.ingest.getPipeline({ + id: pipelineName, + }); + + return pipelinesResponse; + } catch (error) { + // If we can't find anything, we return an empty object + return {}; + } +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts index 435fb0892019..a55b67b79830 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts @@ -11,6 +11,9 @@ import { RequestHandlerContext } from '@kbn/core/server'; import { ErrorCode } from '../../../common/types/error_codes'; +jest.mock('../../lib/indices/fetch_ml_inference_pipeline_history', () => ({ + fetchMlInferencePipelineHistory: jest.fn(), +})); jest.mock('../../lib/indices/fetch_ml_inference_pipeline_processors', () => ({ fetchMlInferencePipelineProcessors: jest.fn(), })); @@ -23,10 +26,15 @@ jest.mock('../../lib/indices/delete_ml_inference_pipeline', () => ({ jest.mock('../../lib/indices/exists_index', () => ({ indexOrAliasExists: jest.fn(), })); +jest.mock('../../lib/ml_inference_pipeline/get_inference_errors', () => ({ + getMlInferenceErrors: jest.fn(), +})); import { deleteMlInferencePipeline } from '../../lib/indices/delete_ml_inference_pipeline'; import { indexOrAliasExists } from '../../lib/indices/exists_index'; +import { fetchMlInferencePipelineHistory } from '../../lib/indices/fetch_ml_inference_pipeline_history'; import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; +import { getMlInferenceErrors } from '../../lib/ml_inference_pipeline/get_inference_errors'; import { createAndReferenceMlInferencePipeline } from '../../utils/create_ml_inference_pipeline'; import { ElasticsearchResponseError } from '../../utils/identify_exceptions'; @@ -40,6 +48,7 @@ describe('Enterprise Search Managed Indices', () => { putPipeline: jest.fn(), simulate: jest.fn(), }, + search: jest.fn(), }, }; @@ -47,6 +56,64 @@ describe('Enterprise Search Managed Indices', () => { elasticsearch: { client: mockClient }, }; + describe('GET /internal/enterprise_search/indices/{indexName}/ml_inference/errors', () => { + beforeEach(() => { + const context = { + core: Promise.resolve(mockCore), + } as unknown as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'get', + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/errors', + }); + + registerIndexRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('fails validation without index_name', () => { + const request = { + params: {}, + }; + mockRouter.shouldThrow(request); + }); + + it('fetches ML inference errors', async () => { + const errorsResult = [ + { + message: 'Error message 1', + doc_count: 100, + timestamp: '2022-10-05T13:50:36.100Z', + }, + { + message: 'Error message 2', + doc_count: 200, + timestamp: '2022-10-05T13:50:36.200Z', + }, + ]; + + (getMlInferenceErrors as jest.Mock).mockImplementationOnce(() => { + return Promise.resolve(errorsResult); + }); + + await mockRouter.callRoute({ + params: { indexName: 'my-index-name' }, + }); + + expect(getMlInferenceErrors).toHaveBeenCalledWith('my-index-name', mockClient.asCurrentUser); + + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: { + errors: errorsResult, + }, + headers: { 'content-type': 'application/json' }, + }); + }); + }); + describe('GET /internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', () => { beforeEach(() => { const context = { @@ -470,4 +537,80 @@ describe('Enterprise Search Managed Indices', () => { }); }); }); + + describe('GET /internal/enterprise_search/indices/{indexName}/ml_inference/history', () => { + beforeEach(() => { + const context = { + core: Promise.resolve(mockCore), + } as unknown as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'get', + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/history', + }); + + registerIndexRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('fails validation without indexName', () => { + const request = { + params: {}, + }; + + mockRouter.shouldThrow(request); + }); + + it('fetches ML inference history', async () => { + const historyResult = { + history: [ + { + pipeline: 'test-001', + doc_count: 10, + }, + { + pipeline: 'test-002', + doc_count: 13, + }, + ], + }; + + (fetchMlInferencePipelineHistory as jest.Mock).mockResolvedValueOnce(historyResult); + + await mockRouter.callRoute({ + params: { indexName: 'unit-test-index' }, + }); + + expect(fetchMlInferencePipelineHistory).toHaveBeenCalledWith( + mockClient.asCurrentUser, + 'unit-test-index' + ); + + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: historyResult, + headers: { 'content-type': 'application/json' }, + }); + }); + + it('fails if fetching history fails', async () => { + (fetchMlInferencePipelineHistory as jest.Mock).mockRejectedValueOnce(new Error('Oh No!!!')); + + await mockRouter.callRoute({ + params: { indexName: 'unit-test-index' }, + }); + + expect(mockRouter.response.customError).toHaveBeenCalledWith({ + body: { + attributes: { + error_code: 'uncaught_exception', + }, + message: 'Enterprise Search encountered an error. Check Kibana Server logs for details.', + }, + statusCode: 502, + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index db46da11f5f5..4ae42edf16ae 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -9,10 +9,12 @@ import { IngestPutPipelineRequest, IngestSimulateRequest, } from '@elastic/elasticsearch/lib/api/types'; + import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; +import { DEFAULT_PIPELINE_NAME } from '../../../common/constants'; import { ErrorCode } from '../../../common/types/error_codes'; import { deleteConnectorById } from '../../lib/connectors/delete_connector'; @@ -24,10 +26,13 @@ import { deleteMlInferencePipeline } from '../../lib/indices/delete_ml_inference import { indexOrAliasExists } from '../../lib/indices/exists_index'; import { fetchIndex } from '../../lib/indices/fetch_index'; import { fetchIndices } from '../../lib/indices/fetch_indices'; +import { fetchMlInferencePipelineHistory } from '../../lib/indices/fetch_ml_inference_pipeline_history'; import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; import { generateApiKey } from '../../lib/indices/generate_api_key'; +import { getMlInferenceErrors } from '../../lib/ml_inference_pipeline/get_inference_errors'; import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions'; import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines'; +import { getPipeline } from '../../lib/pipelines/get_pipeline'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; import { @@ -293,9 +298,15 @@ export function registerIndexRoutes({ elasticsearchErrorHandler(log, async (context, request, response) => { const indexName = decodeURIComponent(request.params.indexName); const { client } = (await context.core).elasticsearch; - const pipelines = await getCustomPipelines(indexName, client); + const [defaultPipeline, customPipelines] = await Promise.all([ + getPipeline(DEFAULT_PIPELINE_NAME, client), + getCustomPipelines(indexName, client), + ]); return response.ok({ - body: pipelines, + body: { + ...defaultPipeline, + ...customPipelines, + }, headers: { 'content-type': 'application/json' }, }); }) @@ -516,6 +527,30 @@ export function registerIndexRoutes({ }) ); + router.get( + { + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/errors', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.indexName); + const { client } = (await context.core).elasticsearch; + + const errors = await getMlInferenceErrors(indexName, client.asCurrentUser); + + return response.ok({ + body: { + errors, + }, + headers: { 'content-type': 'application/json' }, + }); + }) + ); + router.put( { path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors/{pipelineName}', @@ -615,4 +650,26 @@ export function registerIndexRoutes({ } }) ); + + router.get( + { + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/history', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.indexName); + const { client } = (await context.core).elasticsearch; + + const history = await fetchMlInferencePipelineHistory(client.asCurrentUser, indexName); + + return response.ok({ + body: history, + headers: { 'content-type': 'application/json' }, + }); + }) + ); } diff --git a/x-pack/plugins/files/common/index.ts b/x-pack/plugins/files/common/index.ts index 8a97030efff6..be06c5708ce0 100755 --- a/x-pack/plugins/files/common/index.ts +++ b/x-pack/plugins/files/common/index.ts @@ -21,6 +21,7 @@ export type { FileSavedObject, BaseFileMetadata, FileShareOptions, + FileImageMetadata, FileUnshareOptions, BlobStorageSettings, UpdatableFileMetadata, diff --git a/x-pack/plugins/files/common/types.ts b/x-pack/plugins/files/common/types.ts index fd1948e6333e..53f97962597a 100644 --- a/x-pack/plugins/files/common/types.ts +++ b/x-pack/plugins/files/common/types.ts @@ -536,3 +536,22 @@ export interface FilesMetrics { */ countByExtension: Record; } + +/** + * Set of metadata captured for every image uploaded via the file services' + * public components. + */ +export interface FileImageMetadata { + /** + * The blurhash that can be displayed while the image is loading + */ + blurhash?: string; + /** + * Width, in px, of the original image + */ + width: number; + /** + * Height, in px, of the original image + */ + height: number; +} diff --git a/x-pack/plugins/files/public/components/context.tsx b/x-pack/plugins/files/public/components/context.tsx index ceed14b52abb..e55c0c45e4da 100644 --- a/x-pack/plugins/files/public/components/context.tsx +++ b/x-pack/plugins/files/public/components/context.tsx @@ -21,7 +21,6 @@ export const useFilesContext = () => { } return ctx; }; - export const FilesContext: FunctionComponent = ({ children }) => { return ( = ({ + visible, + hash, + width, + height, + isContainerWidth, +}) => { + const ref = useRef(null); + const { euiTheme } = useEuiTheme(); + useEffect(() => { + try { + const { width: blurWidth, height: blurHeight } = fitToBox(width, height); + const canvas = document.createElement('canvas'); + canvas.width = blurWidth; + canvas.height = blurHeight; + const ctx = canvas.getContext('2d')!; + const imageData = ctx.createImageData(blurWidth, blurHeight); + imageData.data.set(decode(hash, blurWidth, blurHeight)); + ctx.putImageData(imageData, 0, 0); + ref.current!.src = canvas.toDataURL(); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + }, [hash, width, height]); + return ( + + ); +}; diff --git a/x-pack/plugins/files/public/components/image/components/img.tsx b/x-pack/plugins/files/public/components/image/components/img.tsx new file mode 100644 index 000000000000..295b062ca1fd --- /dev/null +++ b/x-pack/plugins/files/public/components/image/components/img.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 from 'react'; +import type { ImgHTMLAttributes, MutableRefObject } from 'react'; +import type { EuiImageSize } from '@elastic/eui/src/components/image/image_types'; +import { useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { sizes } from '../styles'; + +export interface Props extends ImgHTMLAttributes { + hidden: boolean; + size?: EuiImageSize; + observerRef: (el: null | HTMLImageElement) => void; +} + +export const Img = React.forwardRef( + ({ observerRef, src, hidden, size, ...rest }, ref) => { + const { euiTheme } = useEuiTheme(); + const styles = [ + css` + transition: opacity ${euiTheme.animation.extraFast}; + `, + hidden + ? css` + visibility: hidden; + ` + : undefined, + size ? sizes[size] : undefined, + ]; + return ( + { + observerRef(element); + if (ref) { + if (typeof ref === 'function') ref(element); + else (ref as MutableRefObject).current = element; + } + }} + /> + ); + } +); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts b/x-pack/plugins/files/public/components/image/components/index.ts similarity index 65% rename from x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts rename to x-pack/plugins/files/public/components/image/components/index.ts index 553cf2edde84..7fee8f7fd63f 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts +++ b/x-pack/plugins/files/public/components/image/components/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -export { - getServiceNowITSMConnectorType, - getServiceNowSIRConnectorType, - getServiceNowITOMConnectorType, -} from './servicenow'; +export { Img } from './img'; +export type { Props as ImgProps } from './img'; +export { Blurhash } from './blurhash'; diff --git a/x-pack/plugins/files/public/components/image/image.constants.stories.tsx b/x-pack/plugins/files/public/components/image/image.constants.stories.tsx index 9b4c2d3b64af..cc5ff23cd281 100644 --- a/x-pack/plugins/files/public/components/image/image.constants.stories.tsx +++ b/x-pack/plugins/files/public/components/image/image.constants.stories.tsx @@ -5,4 +5,11 @@ * 2.0. */ +export function getImageData(): Blob { + const byteChars = window.atob(base64dLogo); + const charpoints = new Array(byteChars.length); + for (let x = 0; x < byteChars.length; x++) charpoints[x] = byteChars.charCodeAt(x); + return new Blob([new Uint8Array(charpoints)], { type: 'image/png' }); +} + export const base64dLogo = `iVBORw0KGgoAAAANSUhEUgAAA4QAAAH0BAMAAACX3f7gAAAAMFBMVEWUyD/g4+JSUlKtqasXqODvUZim2+P154Zty9I2krMHeaFUtdL+0Qoku7EAAAD////6ku7MAAAUTUlEQVR4XuzRoREAIQwEwO/kyzxJg/QTGSQWm5ndFvbr4VCoEIUoVIhCFKJQIQpRiEKFKEQhChWiEIUoVIhCFKJQIQpRiEKFKEQhChWiEIUoVIhCFKJQIQpRiEKFKEQhChWiEIUoVIhCFKJQIQpRiMIXClGIQoUoRCEKFaIQhShUiEIUolAhClGIQoUoRCEKFaIQhShUiEIUolAhClGIQoUoRCEKFaIQhShUiEIUolAhClGIQoUorEqyVo+ksLKv/D2MwsPO/aRIEQRRGD9D09ROKNADiAy1tc/QDLn2GG7qcq5mE+AZvMBAWhS9EVBoh6dSaY4Dbb2A713hR0RGRP+5zPF7HkxrEcI+oBAByEP4MbaTpBAhvMgsVyFCKMGchhBKMKchhBLMaQihBFMbQlgjchtCOMdz8hkAS8K+oP9+COG3eG5GAPwINcokfQ4hVBvN+RxCqDaa9jmEsIonZyuFcI5IXYYQXiKSliGEKsKcZQihijBpGUKoIkxdhhBe4iUBwIZQRehfhhAe7sqP3I2NInR/DSEcylPux0YRWpchhFNRyq+GEe5lCKEElXeN66jtQANh+TOnRh/1/MACQr2Dyqhhxr+TQriWjTT6qONAA2EtWzlpmHHvpBAOm4SjCM07KYRL6RC6d1IIp21CTTPmnRTCpXQI3TsphEOLUAOp9XYPYW0InkXo3UkhXDuE9p0UwvJXwjncOymEtUX4QYTWawWEa4/Q+jGEUEth+77m/hhCWFoZcxBCuLQJtdk7zzMQrqV0jjPm8wyEQ5fQfJ6BsCl4r80+52MI4TkHIYS1QziH+TwD4VJ6m735PAPhmp8Qwt5xxnwkhXC41XFG+bQnAIQ1wnwkhXDqbvY7jaQEQgj7xxmPf9WDsH+cybhVUIUifJWVEEJt9v9lMSQQLq+vOUHYOM7YE359vOYtq/1NCB92IYRwzEII4U03ewjr4zVfbke47kwIIYQQLr3jjPuRFMLaIXStQgiVvxLOCaoQwmPjOONPCKEQp6SEECqH5mZvTgihUicIX5Djm2sMCIX4L79Mg/D9T6B9CZU6NDZ7/6UCQiGKMFUVQqjUY+fLT95VCKEWxXNCQgiVwxPh/J29O8psIIrCOL6JgRCi3Un5VBh0CVVGBJgNFDDmNfraBQS3r1WRTXQtl7pJJag4ZtBm3ENOOKPffws/xznDZcZISEJBfBwJIQmX5WIA0ZLw8z8T3ko31yFMDYCyHSbUGpFQlT1hrAAI4jgJSVihr51lfsHs8TU3CQtIw4g714Qk3ONXbT0qQhIm/O2jzhB6WoUkfMe5Nj2i94fAJDwAyCM+u92EJIwYbv7UEx7dDiEJC0CDuPM6hCRMAFSIXm8ZElbIVVotw6uAkTAhn3zpT30OIQkL5FvJMrS6ZZaWXiSMUHQnhEa3zD68zey8SHiAorlwGw3hNlgakrCBJiGcmgzhdwiGhiRMANTL8Ghyy7yGU2sjLhJ+QdXD5cuwA5gE6cWGi4QNVN0LgMEQxtBVW2iRMELZxR/3HcAPMffv4sYRxQH8b3DWxBCSG24WXO4hLVwpNSIE8mf4KoOjMs01aVIETP4E4ybk3BiDi3QmlUt3xhC20AquXLG7YXNBycX2WbqvRvNr31vN5HFgjNDuMZ979+Y7K+7hLeEFVyh5kIxOqYTJzbtGufpP74lc37xJxCGs7yQj0Zdw3ZdwjmE4KFB0Gz/ur9J6LDc+MhUUwu8qY5Vy5Pdrx1m1LSnT9HQUjrAby83NyjQXPQjP+hK+wjAcFCh+2iG8EHRABUCK/oSTylalBzGRRno5vpVMTvOPX1vCT//D1w7E5qWpDbDFvTaMIx8hjEIOQzTh+tluPSUKJtn+QqYDCFGpS3BcWWu5AcgqZ037fhS4Nt5LghGEeqQIfNL9O1I9iryj+cy5+nxCWOhVu3jKwxK2mf/bAyFGIXsY8gMF6he+IFafPwtRxw7BWF2YwIxAiFEYchgiUNykeuY0bC0A94d2IbR1QbwreBe2FYGQnApx0l0P2st8ATxyvq9dKzScsBSWmBCrC+uMQYhUSDjpPj9IoGBkwwlh9RmE1bF5pkbrwknFIuxmhBJcwtdI9Xr9bOIiruaCNgv9bYjGiNaFTcUjxAEp47EvP1D4NzTE1RSDuhDe2v4pShf6L7HwEoY96X6tBwpvvCeu5uIQhEtDE0brwqZiEp7N6MPwHjdQ/P3MWK/8fv5FEnxC1Fxb1nhdmHEJX84ohY+TDgoUnGjY7B9uSdX0eOAsVMEBE6sL28pZKzvhjFRzliEChbkuqNvRdL45jNpRLPs8qTjN97/yVCogjmWVaZrnaZpKGaILj1QyKfP847cnpY+wphLCkBsoeHvSTkkQptw9NRPSjl1VwusKpT4U+fwDppTZkC50NbxyJHonGcvMTtjNiNsZGLKfUOj1hLKZWQpzc67ohPrB5NTS+SUMlKq3a/3A86TCR9j5zhishFcUwW+V69A/d2itXwmbmVLYXuARYg7p4hAUof9oSaOM12CEezvH86GBAkU4W5tbl1+wCTGIFqosbhqMUG94QSNcc5sQhvxAgZr33o8e25f/hE9YGzugAWxwQuV3OYnwT9LZjGZIDBT8YThxnIFhodmEuL5xNyPCEQIWtAEIkes1Q36goD27z8Bk5V3yCdFw5gsHJ8QoFFTCu6RAodf3lEDB3s9gKgnn8vMJcQODx0lwQjT85X/hCL/etvxvqiHtCQUr3AOkNAJgilAI/dfIwBqacIKBHooQTfhV8U5phXuEQMHezxzp2c/ULSRC/zXQ3cEJM9yLSnhGeGJ/U3VR7Bn+RQgUvPMZIM3dwGEIywiEyIThunCL8aj4UH/M+xkKJVCwtqT+xQTXcgDhtU5YYzwFJkSoWYUiRKrvik+lGnIDBep5r9i2dL9csglBvtKT/SI8YYtRHIgQqf5xYTKsCZ875Dxvaj0/o1lQwlV4woYQKVi5EE14VdzWD4rhuTdQsFOF3wNbRyqhs+W6eITXmBSBCL9BE6qGqHN/oOCniiMQOV+fDu/C5f/ShUeBCREo/i1260fVkBMoUIMWBnsRfhdGJzTJLxiEa1KqL9R66zJEoBhMiFlnqeZwhJcG1fCEGRqeTHjFbkLd8Ev7X0bwl+hBuPQRrg7ahV18whMGYUdK9Vq9Uwz/cQcK/vGMbyi1XEKnVx0v2hMyBeuzMwKpXi3XQQ0CxWBCCBFyI78LUVWsM9KaQYjyC75AqjcZmkP+GwSKAxB6OQ5HeGkgPIlGKDiEL+mpHmU4bLMHCv4haeP9GQ3ThVms54VtBXk64Rk11etlMESgiEKILSt/FjoIKxGJsGQR3iWner3U5bcFCj7hNYuQ34XwgGtQwoZMSMn2W571e+bOYLXJIIrC72CLuPLHBrpssN0nj+A7aAFRbB7BTTZFJPgE0k1pKVCEIrQgfQY34kYkCXQrf1tCVeioFB3qF3tz/zNO5iwLJdCPO6fn3pkbgLuhUYNAYas7A8KQAeHJ1JvArZIRXsyW6kPkZTN8jkChVeFdE2EncRXybmeVA+G4EcJ61lk9dEPIR6Cw1RMMJrpWU4QLD1cjwqlPKkZrxSIMB47WmoMhAoWOcGRD9iOsH/1+eUKEtWO9kITw0jFc9m68qOwiZKMmBgqHKi9CvQrrtT+LsqJOUNyOSlQRDpshPJ+htcZUbzRqEChMubrcehX+pGc89I0mjCdHpSGc2KmerTWjUYNAYStkOEi5UY0aW882R60qJULe3/IjDI1Svd2o+eoiuJuvCs+MbU78ZJZiYQjX/a01o1GDQGFrz0Zoy0Zo77A4QZyZpqVuCQhtMxygCG0NYhG6tJ0L4ar1dn7GjRetqiCEE6u19o6k7JB/4UO4lQGhZ2+JXbOjbjkIw4E5q2/C0IdwX0RodGdAxEZoM79XDsJFJdVTb/my3tZxFoRn5EAvFBjODeGFlOqpq9/85kLYy4Aw4nAtgDIYFoEwHCipnhqIyT49QhYhl7wQob3WedQtBeG58QzGqffxG2FcsVBHOPYv8FpqLd/HpGLmJDkuBWGtpHrqo98Mt3NUYf33gvvWWnfqvJC69S+I7UIQhnUt1etmuP9/q5C7nMbLkd5Ml0bXloxPnDPCiZLqqYF71jTIgXAFcyNj5MsvG6G6hSAMfSnV62ZY5UCIv7uN0KY4TDOp0BFe6KmeZvhE7JCyze0QEZ5OI2h4IbWwisKfy7CJ6gupnqqcbdKtHAgvUaseL4yaXIdYDMKJ48JMejM8zoGwg1uhPi+MqpdghgUgDIvGMxiXPjjN0MDRkRDybraAkL3W9ryuP1EHU5/BZDHDnZChCmtEOZcXkjhQzAshO6UP0Frzq+cyw9cZqhArupp5IYfB43IQhnUh1Wtm2HNcC5MRjoJykHIYPJrfbW6qL7TWFDPcCRkQwm9EhGEjIcKzVAjrPp7BNBY2WArnKBZeuBAye0leyJg5V4RkiGcwOcwQZIyXTRrCdtC8kG+A5/m+kOrHVK/p81VdC1MK4BARdiJCyQsRUro4Xt0IawEhhNaaZoZHSoubOIpC2IkI+cPKgRAVrSH0FqFuhnshL8Ku7oVcOUXbzrXxgkKq18zwu9Rc48YLHaHuhTBXAaG8d4ZCqlfN8FAoQmGrJBBG6+qJXkiHLgghi1A3w3D7UClCLrTQD9LUCNvG6erZwfZFRohULyiW9KYQ69EkFatQ90JWoYwwkh+rCNOkeq7BqF806q3RKgrxQn4CI2yGfaR2EepmGFU/nd5reyX8/y5UYWIv1BGeJkYososDJxPijnMs0M6UC/UqbDfczd3TEGJWrwkgAHG3cvIYpkCoeKFZcKciwnYShM9SIaQeX4fY8/IYCQg3EC5FL+zoCJnth0kQvkx3kFJ3jjx5guNaP0LjrqbeYKuM5baub4sZSwh5d1QPhlQMim+CKa6VFCcV+ryQheM68omQd7N0hJ8SIyTEzV8+OAgOrfiD9w/m7lg5ahgIAKi6lCRxb03k3je2PsDfAu5z0NDyARR8CB/BN9AwdJnIMyl90XkGOgTjwG3CnndnJc+dts/JytPK9tqWlh75OjEhbe6on5HvnLZZgdCvRPiVauPj522QRAmDNJqQeXdGerq9Jgld7P6FQyaEUCR9r0NC4B7u0l9/6ujT7SZtYpASAr3g3lcJStzJ1RmlLlIQcQ918kuI9zSh07Ij2vHP8nlC+LtxyI/wD+I2mdBDD2MJw8GI2dredYIX2KAB9Mwhdkftm2wIn9zUU7yTqzE9lBPWxFTVotWdmGi5r9MehIS/RsFUqmQV0qRPY7xSiYh4GaYmkrBcTOSiPv658GS3rOC4NNa0jHAaIZpMCGeAn0qlIuIeVgtqPf9xGrBCTOABhIfMMMb2xKwL4wF9PyUjDNTSUr6w3WkJoTjzqJ5HyuVpTawLWvTtvLIoTTiBUMetiuhe5q0z7QHSW0N9bL8/gns1CzCEty/ar5p/dq2Z29vlQZhyeVqitQ6qyswBADRh+H+Y+x40jhDCpAiSBoF3xHRhrLWtcSCACLE9BDR2JsJviDANcRr50CQhiODgCQlrPCHi4AkD3dD9aQmhOHOp1FqINQ/Q0YR7KeEtqYfnUcL9nicscyKE4sxbpdZC3CcT+vWykKgDlLGEU26EX+C2EMerCELPp+GWIJTNpANOfFqGy/WBJuQP7+H0hJ8owosQEWVaFgJrehaSxbg6gpBPw+H01ZktRagSz4ZyQv4XXC3Pwg0x1OSEocyQ0K9KOMUT8mk46L2YECuAEU5rntDXmRDi4gwOHWW4jyHkTjdQ8XkjJBzoGV9OGKY6O8LHJcIPISpaZl5jCX+Q64pe1xLCQXMXXjwhNsyH8DtHGG0oz0J+85Dnm8jMccMTEjuOFAJCzJ9O6NcvzsjvKrj/Dg4zVxVZwlCILKxZYHSN8CgZQgh7tHdWCwjDioSXqxMGjxcjdMY2kvtL4U5a1qLKqLNa0shoKi3ooa3R4DzHS4h3qYT0hmeHQnBrXxMA5JKUzlQgwUXRV38hjbFd4KM3MMB0EMbV/ODFGVPZ5myvAt/9Zu8OUhoGAigM21WXVQTrpswZikcSRJChRyju3ARv4654jUIv0WxioJTnsgUtFTtvMgP/f4WPlOQxdA77WgLCOrudPzzOgwydI4zGceZASKUTLgYkhHDmHWf8hBDuUxCGAQkh7L37mp8Qws4/zlwtZQzCNgPhQsYglHlf8z+FEDauw0+HgpxBGO3jzJWsQbj3Hn7yE0LY2/e1kaxB2Na+r0Go6scZCKN7nAlKEITOoXtzZpyROQh78zgzkjkIW/vhJ3MQqvEefgpyB2H0jjOyB2FvJZzIHoSddZwJsgehVsZxZqwMQTgzEk6UIQh7476mHEEo3742UZYgjLZxRnmCcOciXCpPELamfW2sTEGoePFnYcu2Nixhf+kLqfTFMDMooRJcb7DlzMyghLME195thxSEsEtx08/U+TIKoetQ96eOujn594f+IOzT3Jg2vR4KEEIluOjHG4Seg/lBxQRh++9lxh+Evu+KhQoKwrb2hxBC7Wp/CCFUya+jEDrG7o3yBaFlonlXcUHY1f4uA6F2tX/VQ6im9p9RCDt2mQIJLYZrlRqEuv+TYFCxQaiX2mcZCBVrF4RQsXZBCBVrF4RQz6cB11UIQqi7U4IfQVUEodrm10fwVbUEodQ1PwCfVFMQSt3b6gjwuz06pgEABgEApmES9yBlwTUke9BA0lpo3lpD4TiR31v6p3CgEIUKUYhCFCpEIQpRqBCFKEShQhSiEIUKUYhCFCpEIQpRqBCFKEShQhSiEIUKUYhCFCpEIQpRqBCFKEShQhSiEIUKUYhCFCpEIQpRqBCFKEShQhSiEIUKUYhCFCpEIQpRqBCFKEShQhSiEIUKUYhCFCpEIQpRqBCFKEShQhSiEIUKUYhCFCpEIQpRqBCFKEShQhSikAYWVezJjtqBOgAAAABJRU5ErkJggg==`; diff --git a/x-pack/plugins/files/public/components/image/image.stories.tsx b/x-pack/plugins/files/public/components/image/image.stories.tsx index 400b7eb96320..02daf7badb32 100644 --- a/x-pack/plugins/files/public/components/image/image.stories.tsx +++ b/x-pack/plugins/files/public/components/image/image.stories.tsx @@ -5,38 +5,78 @@ * 2.0. */ import React from 'react'; -import { ComponentStory } from '@storybook/react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { css } from '@emotion/react'; +import { FilesContext } from '../context'; +import { getImageMetadata } from '../util'; import { Image, Props } from './image'; -import { base64dLogo } from './image.constants.stories'; +import { getImageData as getBlob, base64dLogo } from './image.constants.stories'; -const defaultArgs = { alt: 'my alt text', src: `data:image/png;base64,${base64dLogo}` }; +const defaultArgs: Props = { alt: 'test', src: `data:image/png;base64,${base64dLogo}` }; export default { title: 'components/Image', component: Image, args: defaultArgs, -}; - -const baseStyle = css` - width: 400px; -`; + decorators: [ + (Story) => ( + + + + ), + ], +} as ComponentMeta; -const Template: ComponentStory = (props: Props) => ( - +const Template: ComponentStory = (props: Props, { loaded: { meta } }) => ( + ); export const Basic = Template.bind({}); +export const WithBlurhash = Template.bind({}); +WithBlurhash.storyName = 'With blurhash'; +WithBlurhash.args = { + style: { visibility: 'hidden' }, +}; +WithBlurhash.loaders = [ + async () => ({ + meta: await getImageMetadata(getBlob()), + }), +]; +WithBlurhash.decorators = [ + (Story) => { + const alwaysShowBlurhash = `img:nth-of-type(1) { opacity: 1 !important; }`; + return ( + <> + + + + ); + }, +]; + export const BrokenSrc = Template.bind({}); +BrokenSrc.storyName = 'Broken src'; BrokenSrc.args = { - src: 'broken', + src: 'foo', }; +export const WithBlurhashAndBrokenSrc = Template.bind({}); +WithBlurhashAndBrokenSrc.storyName = 'With blurhash and broken src'; +WithBlurhashAndBrokenSrc.args = { + src: 'foo', +}; +WithBlurhashAndBrokenSrc.loaders = [ + async () => ({ + blurhash: await getImageMetadata(getBlob()), + }), +]; + export const OffScreen = Template.bind({}); -OffScreen.args = { ...defaultArgs, onFirstVisible: action('visible') }; +OffScreen.storyName = 'Offscreen'; +OffScreen.args = { onFirstVisible: action('visible') }; OffScreen.decorators = [ (Story) => ( <> diff --git a/x-pack/plugins/files/public/components/image/image.tsx b/x-pack/plugins/files/public/components/image/image.tsx index 96ac1a2eee78..915f45c828f6 100644 --- a/x-pack/plugins/files/public/components/image/image.tsx +++ b/x-pack/plugins/files/public/components/image/image.tsx @@ -4,13 +4,30 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { MutableRefObject } from 'react'; -import type { ImgHTMLAttributes } from 'react'; +import React, { HTMLAttributes } from 'react'; +import { type ImgHTMLAttributes, useState, useEffect } from 'react'; +import { css } from '@emotion/react'; +import type { FileImageMetadata } from '../../../common'; import { useViewportObserver } from './use_viewport_observer'; +import { Img, type ImgProps, Blurhash } from './components'; +import { sizes } from './styles'; export interface Props extends ImgHTMLAttributes { src: string; alt: string; + /** + * Image metadata + */ + meta?: FileImageMetadata; + + /** + * @default original + */ + size?: ImgProps['size']; + /** + * Props to pass to the wrapper element + */ + wrapperProps?: HTMLAttributes; /** * Emits when the image first becomes visible */ @@ -28,22 +45,65 @@ export interface Props extends ImgHTMLAttributes { * ``` */ export const Image = React.forwardRef( - ({ src, alt, onFirstVisible, ...rest }, ref) => { + ( + { src, alt, onFirstVisible, onLoad, onError, meta, wrapperProps, size = 'original', ...rest }, + ref + ) => { + const [isLoaded, setIsLoaded] = useState(false); + const [blurDelayExpired, setBlurDelayExpired] = useState(false); const { isVisible, ref: observerRef } = useViewportObserver({ onFirstVisible }); + + useEffect(() => { + let unmounted = false; + const id = window.setTimeout(() => { + if (!unmounted) setBlurDelayExpired(true); + }, 200); + return () => { + unmounted = true; + window.clearTimeout(id); + }; + }, []); + + const knownSize = size ? sizes[size] : undefined; + return ( - { - observerRef(element); - if (ref) { - if (typeof ref === 'function') ref(element); - else (ref as MutableRefObject).current = element; - } - }} - // TODO: We should have a lower resolution alternative to display - src={isVisible ? src : undefined} - alt={alt} - /> +

+ {blurDelayExpired && meta?.blurhash && ( + + )} + { + setIsLoaded(true); + onLoad?.(ev); + }} + onError={(ev) => { + setIsLoaded(true); + onError?.(ev); + }} + {...rest} + /> +
); } ); diff --git a/x-pack/plugins/files/public/components/image/styles.ts b/x-pack/plugins/files/public/components/image/styles.ts new file mode 100644 index 000000000000..b14121c667a5 --- /dev/null +++ b/x-pack/plugins/files/public/components/image/styles.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 { css } from '@emotion/react'; + +// Values taken from @elastic/eui/src/components/image +export const sizes = { + s: css` + width: 100px; + `, + m: css` + width: 200px; + `, + l: css` + width: 360px; + `, + xl: css` + width: 600px; + `, + original: css` + width: auto; + `, + fullWidth: css` + width: 100%; + `, +}; diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx index 8e0d8ed59392..e85460ca7c1e 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx @@ -40,7 +40,7 @@ export interface Props { /** * A files client that will be used process uploads. */ - client: FilesClient; + client: FilesClient; /** * Allow users to clear a file after uploading. * diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts index 8fc0c02e0f60..3a4e19adf811 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts @@ -11,6 +11,7 @@ import { TestScheduler } from 'rxjs/testing'; import type { FileKind, FileJSON } from '../../../common'; import { createMockFilesClient } from '../../mocks'; import type { FilesClient } from '../../types'; +import { ImageMetadataFactory } from '../util/image_metadata'; import { UploadState } from './upload_state'; @@ -21,6 +22,7 @@ describe('UploadState', () => { let filesClient: DeeplyMockedKeys; let uploadState: UploadState; let testScheduler: TestScheduler; + const imageMetadataFactory = (() => of(undefined)) as unknown as ImageMetadataFactory; beforeEach(() => { filesClient = createMockFilesClient(); @@ -28,7 +30,9 @@ describe('UploadState', () => { filesClient.upload.mockReturnValue(of(undefined) as any); uploadState = new UploadState( { id: 'test', http: {}, maxSizeBytes: 1000 } as FileKind, - filesClient + filesClient, + {}, + imageMetadataFactory ); testScheduler = getTestScheduler(); }); @@ -189,7 +193,8 @@ describe('UploadState', () => { uploadState = new UploadState( { id: 'test', http: {}, maxSizeBytes: 1000 } as FileKind, filesClient, - { allowRepeatedUploads: true } + { allowRepeatedUploads: true }, + imageMetadataFactory ); const file1 = { name: 'test' } as File; const file2 = { name: 'test 2.png' } as File; diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.ts index d5fbc04512fd..dd03eb7aee56 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_state.ts +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.ts @@ -29,6 +29,7 @@ import { } from 'rxjs'; import type { FileKind, FileJSON } from '../../../common/types'; import type { FilesClient } from '../../types'; +import { ImageMetadataFactory, getImageMetadata, isImage } from '../util'; import { i18nTexts } from './i18n_texts'; import { createStateSubject, type SimpleStateSubject, parseFileName } from './util'; @@ -68,7 +69,8 @@ export class UploadState { constructor( private readonly fileKind: FileKind, private readonly client: FilesClient, - private readonly opts: UploadOptions = { allowRepeatedUploads: false } + private readonly opts: UploadOptions = { allowRepeatedUploads: false }, + private readonly loadImageMetadata: ImageMetadataFactory = getImageMetadata ) { const latestFiles$ = this.files$$.pipe(switchMap((files$) => combineLatest(files$))); this.subscriptions = [ @@ -171,15 +173,17 @@ export class UploadState { const { name } = parseFileName(file.name); const mime = file.type || undefined; - - return from( - this.client.create({ - kind: this.fileKind.id, - name, - mimeType: mime, - meta: meta as Record, - }) - ).pipe( + const _meta = meta as Record; + + return from(isImage(file) ? this.loadImageMetadata(file) : of(undefined)).pipe( + mergeMap((imageMetadata) => + this.client.create({ + kind: this.fileKind.id, + name, + mimeType: mime, + meta: imageMetadata ? { ...imageMetadata, ..._meta } : _meta, + }) + ), mergeMap((result) => { uploadTarget = result.file; return race( @@ -240,10 +244,12 @@ export class UploadState { export const createUploadState = ({ fileKind, client, + imageMetadataFactory, ...options }: { fileKind: FileKind; client: FilesClient; + imageMetadataFactory?: ImageMetadataFactory; } & UploadOptions) => { - return new UploadState(fileKind, client, options); + return new UploadState(fileKind, client, options, imageMetadataFactory); }; diff --git a/x-pack/plugins/files/public/components/util/image_metadata.test.ts b/x-pack/plugins/files/public/components/util/image_metadata.test.ts new file mode 100644 index 000000000000..16980aee0029 --- /dev/null +++ b/x-pack/plugins/files/public/components/util/image_metadata.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { fitToBox } from './image_metadata'; +describe('util', () => { + describe('fitToBox', () => { + test('300x300', () => { + expect(fitToBox(300, 300)).toMatchInlineSnapshot(` + Object { + "height": 300, + "width": 300, + } + `); + }); + + test('300x150', () => { + expect(fitToBox(300, 150)).toMatchInlineSnapshot(` + Object { + "height": 150, + "width": 300, + } + `); + }); + + test('4500x9000', () => { + expect(fitToBox(4500, 9000)).toMatchInlineSnapshot(` + Object { + "height": 300, + "width": 150, + } + `); + }); + + test('1000x300', () => { + expect(fitToBox(1000, 300)).toMatchInlineSnapshot(` + Object { + "height": 90, + "width": 300, + } + `); + }); + + test('0x0', () => { + expect(fitToBox(0, 0)).toMatchInlineSnapshot(` + Object { + "height": 0, + "width": 0, + } + `); + }); + }); +}); diff --git a/x-pack/plugins/files/public/components/util/image_metadata.ts b/x-pack/plugins/files/public/components/util/image_metadata.ts new file mode 100644 index 000000000000..9358dda9d05a --- /dev/null +++ b/x-pack/plugins/files/public/components/util/image_metadata.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as bh from 'blurhash'; +import type { FileImageMetadata } from '../../../common'; + +export function isImage(file: Blob | File): boolean { + return file.type?.startsWith('image/'); +} + +export const boxDimensions = { + width: 300, + height: 300, +}; + +/** + * Calculate the size of an image, fitting to our limits see {@link boxDimensions}, + * while preserving the aspect ratio. + */ +export function fitToBox(width: number, height: number): { width: number; height: number } { + const offsetRatio = Math.abs( + Math.min( + // Find the aspect at which our box is smallest, if less than 1, it means we exceed the limits + Math.min(boxDimensions.width / width, boxDimensions.height / height), + // Values greater than 1 are within our limits + 1 + ) - 1 // Get the percentage we are exceeding. E.g., 0.3 - 1 = -0.7 means the image needs to shrink by 70% to fit + ); + return { + width: Math.floor(width - offsetRatio * width), + height: Math.floor(height - offsetRatio * height), + }; +} + +/** + * Get the native size of the image + */ +function loadImage(src: string): Promise { + return new Promise((res, rej) => { + const image = new window.Image(); + image.src = src; + image.onload = () => res(image); + image.onerror = rej; + }); +} + +/** + * Extract image metadata, assumes that file or blob as an image! + */ +export async function getImageMetadata(file: File | Blob): Promise { + const imgUrl = window.URL.createObjectURL(file); + try { + const image = await loadImage(imgUrl); + const canvas = document.createElement('canvas'); + const { width, height } = fitToBox(image.width, image.height); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Could not get 2d canvas context!'); + ctx.drawImage(image, 0, 0, width, height); + const imgData = ctx.getImageData(0, 0, width, height); + return { + blurhash: bh.encode(imgData.data, imgData.width, imgData.height, 4, 4), + width: image.width, + height: image.height, + }; + } catch (e) { + // Don't error out if we cannot generate the blurhash + return undefined; + } finally { + window.URL.revokeObjectURL(imgUrl); + } +} + +export type ImageMetadataFactory = typeof getImageMetadata; diff --git a/x-pack/plugins/files/public/components/util/index.ts b/x-pack/plugins/files/public/components/util/index.ts new file mode 100644 index 000000000000..e3e30fdb17bb --- /dev/null +++ b/x-pack/plugins/files/public/components/util/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getImageMetadata, isImage, fitToBox } from './image_metadata'; +export type { ImageMetadataFactory } from './image_metadata'; diff --git a/x-pack/plugins/files/public/plugin.ts b/x-pack/plugins/files/public/plugin.ts index 8ebbd71cdbe1..2f5481f8f251 100644 --- a/x-pack/plugins/files/public/plugin.ts +++ b/x-pack/plugins/files/public/plugin.ts @@ -11,9 +11,10 @@ import { setFileKindsRegistry, FileKindsRegistryImpl, } from '../common/file_kinds_registry'; -import type { FilesClientFactory } from './types'; +import type { FilesClient, FilesClientFactory } from './types'; import { createFilesClient } from './files_client'; import { FileKind } from '../common'; +import { ScopedFilesClient } from '.'; /** * Public setup-phase contract @@ -50,11 +51,11 @@ export class FilesPlugin implements Plugin { setup(core: CoreSetup): FilesSetup { this.filesClientFactory = { - asScoped(fileKind: string) { - return createFilesClient({ fileKind, http: core.http }); + asScoped(fileKind: string) { + return createFilesClient({ fileKind, http: core.http }) as ScopedFilesClient; }, - asUnscoped() { - return createFilesClient({ http: core.http }); + asUnscoped() { + return createFilesClient({ http: core.http }) as FilesClient; }, }; return { diff --git a/x-pack/plugins/files/public/types.ts b/x-pack/plugins/files/public/types.ts index 25aab6e787b6..1cc69ac4ed23 100644 --- a/x-pack/plugins/files/public/types.ts +++ b/x-pack/plugins/files/public/types.ts @@ -60,13 +60,13 @@ interface GlobalEndpoints { /** * A client that can be used to manage a specific {@link FileKind}. */ -export interface FilesClient extends GlobalEndpoints { +export interface FilesClient extends GlobalEndpoints { /** * Create a new file object with the provided metadata. * * @param args - create file args */ - create: ClientMethodFrom; + create: ClientMethodFrom>; /** * Delete a file object and all associated share and content objects. * @@ -78,19 +78,19 @@ export interface FilesClient extends GlobalEndpoints { * * @param args - get file by ID args */ - getById: ClientMethodFrom; + getById: ClientMethodFrom>; /** * List all file objects, of a given {@link FileKind}. * * @param args - list files args */ - list: ClientMethodFrom; + list: ClientMethodFrom>; /** * Update a set of of metadata values of the file object. * * @param args - update file args */ - update: ClientMethodFrom; + update: ClientMethodFrom>; /** * Stream the contents of the file to Kibana server for storage. * @@ -151,8 +151,8 @@ export interface FilesClient extends GlobalEndpoints { listShares: ClientMethodFrom; } -export type FilesClientResponses = { - [K in keyof FilesClient]: Awaited>; +export type FilesClientResponses = { + [K in keyof FilesClient]: Awaited[K]>>; }; /** @@ -161,10 +161,10 @@ export type FilesClientResponses = { * More convenient if you want to re-use the same client for the same file kind * and not specify the kind every time. */ -export type ScopedFilesClient = { +export type ScopedFilesClient = { [K in keyof FilesClient]: K extends 'list' - ? (arg?: Omit[0], 'kind'>) => ReturnType - : (arg: Omit[0], 'kind'>) => ReturnType; + ? (arg?: Omit[K]>[0], 'kind'>) => ReturnType[K]> + : (arg: Omit[K]>[0], 'kind'>) => ReturnType[K]>; }; /** @@ -174,11 +174,11 @@ export interface FilesClientFactory { /** * Create a files client. */ - asUnscoped(): FilesClient; + asUnscoped(): FilesClient; /** * Create a {@link ScopedFileClient} for a given {@link FileKind}. * * @param fileKind - The {@link FileKind} to create a client for. */ - asScoped(fileKind: string): ScopedFilesClient; + asScoped(fileKind: string): ScopedFilesClient; } diff --git a/x-pack/plugins/files/server/blob_storage_service/adapters/es/es.ts b/x-pack/plugins/files/server/blob_storage_service/adapters/es/es.ts index adc707f4b141..96421a06e95c 100644 --- a/x-pack/plugins/files/server/blob_storage_service/adapters/es/es.ts +++ b/x-pack/plugins/files/server/blob_storage_service/adapters/es/es.ts @@ -49,10 +49,6 @@ export class ElasticsearchBlobStorageClient implements BlobStorageClient { */ private readonly uploadSemaphore = ElasticsearchBlobStorageClient.defaultSemaphore ) { - assert( - this.index.startsWith('.kibana'), - `Elasticsearch blob store index name must start with ".kibana", got ${this.index}.` - ); assert(this.uploadSemaphore, `No default semaphore provided and no semaphore was passed in.`); } diff --git a/x-pack/plugins/files/server/file_client/integration_tests/es_file_client.test.ts b/x-pack/plugins/files/server/file_client/integration_tests/es_file_client.test.ts index 25e6e1b5d4c1..58bf5cec29c4 100644 --- a/x-pack/plugins/files/server/file_client/integration_tests/es_file_client.test.ts +++ b/x-pack/plugins/files/server/file_client/integration_tests/es_file_client.test.ts @@ -11,11 +11,7 @@ import { createEsFileClient } from '../create_es_file_client'; import { FileClient } from '../types'; import { FileMetadata } from '../../../common'; -/** - * This file client is using Elasticsearch interfaces directly to manage files. - */ -// FLAKY: https://github.com/elastic/kibana/issues/139565 -describe.skip('ES-index-backed file client', () => { +describe('ES-index-backed file client', () => { let esClient: TestEnvironmentUtils['esClient']; let fileClient: FileClient; let testHarness: TestEnvironmentUtils; @@ -197,6 +193,9 @@ describe.skip('ES-index-backed file client', () => { }) ); - await Promise.all([fileClient.delete({ id: id1 }), fileClient.delete({ id: id2 })]); + await Promise.all([ + fileClient.delete({ id: id1, hasContent: false }), + fileClient.delete({ id: id2, hasContent: false }), + ]); }); }); diff --git a/x-pack/plugins/files/server/routes/common.test.ts b/x-pack/plugins/files/server/routes/common.test.ts index 2c4d302d0462..1f9292e3ff07 100644 --- a/x-pack/plugins/files/server/routes/common.test.ts +++ b/x-pack/plugins/files/server/routes/common.test.ts @@ -26,30 +26,30 @@ describe('getDownloadHeadersForFile', () => { const file = { data: { name: 'test', mimeType: undefined } } as unknown as File; test('no mime type and name from file object', () => { - expect(getDownloadHeadersForFile(file, undefined)).toEqual( + expect(getDownloadHeadersForFile({ file, fileName: undefined })).toEqual( expectHeaders({ contentType: 'application/octet-stream', contentDisposition: 'test' }) ); }); test('no mime type and name (without ext)', () => { - expect(getDownloadHeadersForFile(file, 'myfile')).toEqual( + expect(getDownloadHeadersForFile({ file, fileName: 'myfile' })).toEqual( expectHeaders({ contentType: 'application/octet-stream', contentDisposition: 'myfile' }) ); }); test('no mime type and name (with ext)', () => { - expect(getDownloadHeadersForFile(file, 'myfile.png')).toEqual( + expect(getDownloadHeadersForFile({ file, fileName: 'myfile.png' })).toEqual( expectHeaders({ contentType: 'image/png', contentDisposition: 'myfile.png' }) ); }); test('mime type and no name', () => { const fileWithMime = { data: { ...file.data, mimeType: 'application/pdf' } } as File; - expect(getDownloadHeadersForFile(fileWithMime, undefined)).toEqual( + expect(getDownloadHeadersForFile({ file: fileWithMime, fileName: undefined })).toEqual( expectHeaders({ contentType: 'application/pdf', contentDisposition: 'test' }) ); }); test('mime type and name', () => { const fileWithMime = { data: { ...file.data, mimeType: 'application/pdf' } } as File; - expect(getDownloadHeadersForFile(fileWithMime, 'a cool file.pdf')).toEqual( + expect(getDownloadHeadersForFile({ file: fileWithMime, fileName: 'a cool file.pdf' })).toEqual( expectHeaders({ contentType: 'application/pdf', contentDisposition: 'a cool file.pdf' }) ); }); diff --git a/x-pack/plugins/files/server/routes/common.ts b/x-pack/plugins/files/server/routes/common.ts index 0730a6435de0..8e17a39511b5 100644 --- a/x-pack/plugins/files/server/routes/common.ts +++ b/x-pack/plugins/files/server/routes/common.ts @@ -8,7 +8,12 @@ import mime from 'mime'; import type { ResponseHeaders } from '@kbn/core/server'; import type { File } from '../../common/types'; -export function getDownloadHeadersForFile(file: File, fileName?: string): ResponseHeaders { +interface Args { + file: File; + fileName?: string; +} + +export function getDownloadHeadersForFile({ file, fileName }: Args): ResponseHeaders { return { 'content-type': (fileName && mime.getType(fileName)) ?? file.data.mimeType ?? 'application/octet-stream', diff --git a/x-pack/plugins/files/server/routes/file_kind/create.ts b/x-pack/plugins/files/server/routes/file_kind/create.ts index a134bdd292e9..78a7260771a1 100644 --- a/x-pack/plugins/files/server/routes/file_kind/create.ts +++ b/x-pack/plugins/files/server/routes/file_kind/create.ts @@ -23,7 +23,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; +export type Endpoint = CreateRouteDefinition }>; export const handler: CreateHandler = async ({ fileKind, files }, req, res) => { const { fileService } = await files; diff --git a/x-pack/plugins/files/server/routes/file_kind/download.ts b/x-pack/plugins/files/server/routes/file_kind/download.ts index 85f8b5bd0a2d..d4ae37ddb662 100644 --- a/x-pack/plugins/files/server/routes/file_kind/download.ts +++ b/x-pack/plugins/files/server/routes/file_kind/download.ts @@ -40,7 +40,7 @@ export const handler: CreateHandler = async ({ files, fileKind }, req, const body: Response = await file.downloadContent(); return res.ok({ body, - headers: getDownloadHeadersForFile(file, fileName), + headers: getDownloadHeadersForFile({ file, fileName }), }); } catch (e) { if (e instanceof fileErrors.NoDownloadAvailableError) { diff --git a/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts b/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts index 00c5bd2312f8..4d86e05564fd 100644 --- a/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts +++ b/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts @@ -19,7 +19,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; +export type Endpoint = CreateRouteDefinition }>; export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { fileService } = await files; diff --git a/x-pack/plugins/files/server/routes/file_kind/list.ts b/x-pack/plugins/files/server/routes/file_kind/list.ts index 3f1e36913bdc..b6a869117b37 100644 --- a/x-pack/plugins/files/server/routes/file_kind/list.ts +++ b/x-pack/plugins/files/server/routes/file_kind/list.ts @@ -18,7 +18,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; +export type Endpoint = CreateRouteDefinition> }>; export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { diff --git a/x-pack/plugins/files/server/routes/file_kind/update.ts b/x-pack/plugins/files/server/routes/file_kind/update.ts index 733f9c9ce78c..9621fc56c311 100644 --- a/x-pack/plugins/files/server/routes/file_kind/update.ts +++ b/x-pack/plugins/files/server/routes/file_kind/update.ts @@ -26,7 +26,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; +export type Endpoint = CreateRouteDefinition }>; export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { fileService } = await files; diff --git a/x-pack/plugins/files/server/routes/public_facing/download.ts b/x-pack/plugins/files/server/routes/public_facing/download.ts index bd739f93077f..bd77cabaf7e4 100644 --- a/x-pack/plugins/files/server/routes/public_facing/download.ts +++ b/x-pack/plugins/files/server/routes/public_facing/download.ts @@ -43,7 +43,7 @@ const handler: CreateHandler = async ({ files }, req, res) => { const body: Readable = await file.downloadContent(); return res.ok({ body, - headers: getDownloadHeadersForFile(file, fileName), + headers: getDownloadHeadersForFile({ file, fileName }), }); } catch (e) { if ( diff --git a/x-pack/plugins/fleet/.storybook/context/index.tsx b/x-pack/plugins/fleet/.storybook/context/index.tsx index 1d5416cf0483..411f0030ccb6 100644 --- a/x-pack/plugins/fleet/.storybook/context/index.tsx +++ b/x-pack/plugins/fleet/.storybook/context/index.tsx @@ -15,6 +15,7 @@ import { I18nProvider } from '@kbn/i18n-react'; import { CoreScopedHistory } from '@kbn/core/public'; import { getStorybookContextProvider } from '@kbn/custom-integrations-plugin/storybook'; +import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks'; import { IntegrationsAppContext } from '../../public/applications/integrations/app'; import type { FleetConfigType, FleetStartServices } from '../../public/plugin'; @@ -110,6 +111,7 @@ export const StorybookContext: React.FC<{ storyContext?: Parameters writeIntegrationPolicies: true, }, }, + guidedOnboarding: guidedOnboardingMock.createStart(), }), [isCloudEnabled] ); diff --git a/x-pack/plugins/fleet/common/constants/fleet_server_policy_config.ts b/x-pack/plugins/fleet/common/constants/fleet_server_policy_config.ts new file mode 100644 index 000000000000..17a2193bf19c --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/fleet_server_policy_config.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const FLEET_SERVER_HOST_SAVED_OBJECT_TYPE = 'fleet-fleet-server-host'; + +export const DEFAULT_FLEET_SERVER_HOST_ID = 'fleet-default-fleet-server-host'; diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts index 955abb6b7456..01193687125c 100644 --- a/x-pack/plugins/fleet/common/constants/index.ts +++ b/x-pack/plugins/fleet/common/constants/index.ts @@ -16,6 +16,7 @@ export * from './enrollment_api_key'; export * from './settings'; export * from './preconfiguration'; export * from './download_source'; +export * from './fleet_server_policy_config'; export * from './authz'; // TODO: This is the default `index.max_result_window` ES setting, which dictates diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index fa9e07489916..d48e07223cdf 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -88,6 +88,15 @@ export const OUTPUT_API_ROUTES = { LOGSTASH_API_KEY_PATTERN: `${API_ROOT}/logstash_api_keys`, }; +// Fleet server API routes +export const FLEET_SERVER_HOST_API_ROUTES = { + LIST_PATTERN: `${API_ROOT}/fleet_server_hosts`, + CREATE_PATTERN: `${API_ROOT}/fleet_server_hosts`, + INFO_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`, + UPDATE_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`, + DELETE_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`, +}; + // Settings API routes export const SETTINGS_API_ROUTES = { INFO_PATTERN: `${API_ROOT}/settings`, diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 71b4ec04d4e9..b228433c1626 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -3706,6 +3706,230 @@ } ] } + }, + "/fleet_server_hosts": { + "get": { + "summary": "Fleet Server Hosts - List", + "description": "Return a list of Fleet server host", + "tags": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/fleet_server_host" + } + }, + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "perPage": { + "type": "integer" + } + } + } + } + } + } + }, + "operationId": "get-fleet-server-hosts" + }, + "post": { + "summary": "Fleet Server Hosts - Create", + "description": "Create a new Fleet Server Host", + "tags": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "item": { + "$ref": "#/components/schemas/fleet_server_host" + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "is_default": { + "type": "boolean" + }, + "host_urls": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "name", + "host_urls" + ] + } + } + } + }, + "operationId": "post-fleet-server-hosts" + } + }, + "/fleet_server_hosts/{itemId}": { + "get": { + "summary": "Fleet Server Hosts - Info", + "tags": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "item": { + "$ref": "#/components/schemas/fleet_server_host" + } + }, + "required": [ + "item" + ] + } + } + } + } + }, + "operationId": "get-one-fleet-server-hosts" + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "itemId", + "in": "path", + "required": true + } + ], + "delete": { + "summary": "Fleet Server Hosts - Delete", + "operationId": "delete-fleet-server-hosts", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "itemId", + "in": "path", + "required": true + }, + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ] + }, + "put": { + "summary": "Fleet Server Hosts - Update", + "operationId": "update-fleet-server-hosts", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "is_default": { + "type": "boolean" + }, + "host_urls": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "item": { + "$ref": "#/components/schemas/fleet_server_host" + } + }, + "required": [ + "item" + ] + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "itemId", + "in": "path", + "required": true + }, + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ] + } } }, "components": { @@ -3977,9 +4201,6 @@ "has_seen_add_data_notice": { "type": "boolean" }, - "has_seen_fleet_migration_notice": { - "type": "boolean" - }, "fleet_server_hosts": { "type": "array", "items": { @@ -5470,6 +5691,37 @@ "name", "host" ] + }, + "fleet_server_host": { + "title": "Fleet Server Host", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "is_default": { + "type": "boolean" + }, + "is_preconfigured": { + "type": "boolean" + }, + "host_urls": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "fleet_server_hosts", + "id", + "is_default", + "is_preconfigured", + "host_urls" + ] } } }, diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 92347809b061..f5f9dedf462d 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -2289,6 +2289,145 @@ paths: operationId: generate-logstash-api-key parameters: - $ref: '#/components/parameters/kbn_xsrf' + /fleet_server_hosts: + get: + summary: Fleet Server Hosts - List + description: Return a list of Fleet server host + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/fleet_server_host' + total: + type: integer + page: + type: integer + perPage: + type: integer + operationId: get-fleet-server-hosts + post: + summary: Fleet Server Hosts - Create + description: Create a new Fleet Server Host + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: '#/components/schemas/fleet_server_host' + requestBody: + content: + application/json: + schema: + type: object + properties: + id: + type: string + name: + type: string + is_default: + type: boolean + host_urls: + type: array + items: + type: string + required: + - name + - host_urls + operationId: post-fleet-server-hosts + /fleet_server_hosts/{itemId}: + get: + summary: Fleet Server Hosts - Info + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: '#/components/schemas/fleet_server_host' + required: + - item + operationId: get-one-fleet-server-hosts + parameters: + - schema: + type: string + name: itemId + in: path + required: true + delete: + summary: Fleet Server Hosts - Delete + operationId: delete-fleet-server-hosts + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + id: + type: string + required: + - id + parameters: + - schema: + type: string + name: itemId + in: path + required: true + - $ref: '#/components/parameters/kbn_xsrf' + put: + summary: Fleet Server Hosts - Update + operationId: update-fleet-server-hosts + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: string + is_default: + type: boolean + host_urls: + type: array + items: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: '#/components/schemas/fleet_server_host' + required: + - item + parameters: + - schema: + type: string + name: itemId + in: path + required: true + - $ref: '#/components/parameters/kbn_xsrf' components: securitySchemes: basicAuth: @@ -2474,8 +2613,6 @@ components: type: string has_seen_add_data_notice: type: boolean - has_seen_fleet_migration_notice: - type: boolean fleet_server_hosts: type: array items: @@ -3509,5 +3646,27 @@ components: - is_default - name - host + fleet_server_host: + title: Fleet Server Host + type: object + properties: + id: + type: string + name: + type: string + is_default: + type: boolean + is_preconfigured: + type: boolean + host_urls: + type: array + items: + type: string + required: + - fleet_server_hosts + - id + - is_default + - is_preconfigured + - host_urls security: - basicAuth: [] diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/fleet_server_host.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/fleet_server_host.yaml new file mode 100644 index 000000000000..133bc7fcce13 --- /dev/null +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/fleet_server_host.yaml @@ -0,0 +1,21 @@ +title: Fleet Server Host +type: object +properties: + id: + type: string + name: + type: string + is_default: + type: boolean + is_preconfigured: + type: boolean + host_urls: + type: array + items: + type: string +required: + - fleet_server_hosts + - id + - is_default + - is_preconfigured + - host_urls diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index 4d5126d10a41..bccfa64ff772 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -124,6 +124,11 @@ paths: $ref: paths/agent_download_sources@{source_id}.yaml /logstash_api_keys: $ref: paths/logstash_api_keys.yaml + # Fleet server hosts + /fleet_server_hosts: + $ref: paths/fleet_server_hosts.yaml + /fleet_server_hosts/{itemId}: + $ref: paths/fleet_server_hosts@{item_id}.yaml components: securitySchemes: basicAuth: diff --git a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml new file mode 100644 index 000000000000..4ad7d867edc9 --- /dev/null +++ b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml @@ -0,0 +1,57 @@ +get: + summary: Fleet Server Hosts - List + description: Return a list of Fleet server hosts + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + items: + type: array + items: + $ref: ../components/schemas/fleet_server_host.yaml + total: + type: integer + page: + type: integer + perPage: + type: integer + operationId: get-fleet-server-hosts +post: + summary: Fleet Server Hosts - Create + description: 'Create a new Fleet Server Host' + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: ../components/schemas/fleet_server_host.yaml + requestBody: + content: + application/json: + schema: + type: object + properties: + id: + type: string + name: + type: string + is_default: + type: boolean + host_urls: + type: array + items: + type: string + required: + - name + - host_urls + operationId: post-fleet-server-hosts diff --git a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml new file mode 100644 index 000000000000..b2a50f8b52b8 --- /dev/null +++ b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml @@ -0,0 +1,80 @@ +get: + summary: Fleet Server Hosts - Info + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: ../components/schemas/fleet_server_host.yaml + required: + - item + operationId: get-one-fleet-server-hosts +parameters: + - schema: + type: string + name: itemId + in: path + required: true +delete: + summary: Fleet Server Hosts - Delete + operationId: delete-fleet-server-hosts + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + id: + type: string + required: + - id + parameters: + - schema: + type: string + name: itemId + in: path + required: true + - $ref: ../components/headers/kbn_xsrf.yaml +put: + summary: Fleet Server Hosts - Update + operationId: update-fleet-server-hosts + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: string + is_default: + type: boolean + host_urls: + type: array + items: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: ../components/schemas/fleet_server_host.yaml + required: + - item + parameters: + - schema: + type: string + name: itemId + in: path + required: true + - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index ea2ad78dc10c..9e47451aa02b 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -96,10 +96,13 @@ export interface Agent extends AgentBase { access_api_key?: string; // @deprecated default_api_key_history?: FleetServerAgent['default_api_key_history']; - outputs?: Array<{ - api_key_id: string; - to_retire_api_key_ids?: FleetServerAgent['default_api_key_history']; - }>; + outputs?: Record< + string, + { + api_key_id: string; + to_retire_api_key_ids?: FleetServerAgent['default_api_key_history']; + } + >; status?: AgentStatus; packages: string[]; sort?: Array; diff --git a/x-pack/plugins/fleet/common/types/models/fleet_server_policy_config.ts b/x-pack/plugins/fleet/common/types/models/fleet_server_policy_config.ts new file mode 100644 index 000000000000..11930e7bfd63 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/models/fleet_server_policy_config.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface NewFleetServerHost { + name: string; + host_urls: string[]; + is_default: boolean; + is_preconfigured: boolean; +} + +export interface FleetServerHost extends NewFleetServerHost { + id: string; +} + +export type FleetServerHostSOAttributes = NewFleetServerHost; diff --git a/x-pack/plugins/fleet/common/types/models/index.ts b/x-pack/plugins/fleet/common/types/models/index.ts index 07becab0af3f..78429c36dd0a 100644 --- a/x-pack/plugins/fleet/common/types/models/index.ts +++ b/x-pack/plugins/fleet/common/types/models/index.ts @@ -16,3 +16,4 @@ export * from './enrollment_api_key'; export * from './settings'; export * from './preconfiguration'; export * from './download_sources'; +export * from './fleet_server_policy_config'; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/fleet_server_hosts.ts b/x-pack/plugins/fleet/common/types/rest_spec/fleet_server_hosts.ts new file mode 100644 index 000000000000..bf8be3cb3840 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/rest_spec/fleet_server_hosts.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FleetServerHost } from '../models'; + +import type { ListResult } from './common'; + +export type GetFleetServerHostsResponse = ListResult; diff --git a/x-pack/plugins/fleet/dev_docs/data_model.md b/x-pack/plugins/fleet/dev_docs/data_model.md index 483eabb4ed56..a36cda76fffb 100644 --- a/x-pack/plugins/fleet/dev_docs/data_model.md +++ b/x-pack/plugins/fleet/dev_docs/data_model.md @@ -117,6 +117,15 @@ Contains configuration for ingest outputs that can be shared across multiple `in only exposes a single Elasticsearch output that will be used for all package policies, but in the future this may be used for other types of outputs like separate monitoring clusters, Logstash, etc. +### `ingest-download-sources` +- Constant in code: `DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#329) +- Migrations: 8.4.0, 8.5.0 + +Contains configuration for the download source objects that allow users to configure a custom registry +for downloading the Elastic Agent. The default value is for the registry is `https://artifacts.elastic.co/downloads/`. The UI exposes this configuration in Settings. + ### `epm-packages` - Constant in code: `PACKAGES_SAVED_OBJECT_TYPE` diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index 79d8bbd40644..c11cd0d9cdae 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -8,7 +8,7 @@ "server": true, "ui": true, "configPath": ["xpack", "fleet"], - "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging", "taskManager"], + "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging", "taskManager", "guidedOnboarding"], "optionalPlugins": ["features", "cloud", "usageCollection", "home", "globalSearch", "telemetry", "discover", "ingestPipelines"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "cloudChat", "esUiShared", "infra", "kibanaUtils", "usageCollection", "unifiedSearch"] diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx index 011134151cbd..772d90ada7dc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx @@ -27,6 +27,8 @@ import type { SearchHit } from '@kbn/es-types'; import styled from 'styled-components'; +import { useStartServices, useIsGuidedOnboardingActive } from '../../../../../../../hooks'; + import type { PackageInfo } from '../../../../../../../../common'; import { @@ -136,8 +138,15 @@ export const ConfirmIncomingDataWithPreview: React.FunctionComponent = ({ ); const { enrolledAgents, numAgentsWithData } = useGetAgentIncomingData(incomingData, packageInfo); + const isGuidedOnboardingActive = useIsGuidedOnboardingActive(packageInfo?.name); + const { guidedOnboarding } = useStartServices(); if (!isLoading && enrolledAgents > 0 && numAgentsWithData > 0) { setAgentDataConfirmed(true); + if (isGuidedOnboardingActive) { + guidedOnboarding.guidedOnboardingApi?.completeGuidedOnboardingForIntegration( + packageInfo?.name + ); + } } if (!agentDataConfirmed) { return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx index c5ccd2916e46..cd0164124bfb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/add_integration.tsx @@ -217,11 +217,11 @@ export const AddIntegrationPageStep: React.FC = (props return ( extensionView && ( - + ) ); - }, [packagePolicy, extensionView]); + }, [extensionView]); const content = useMemo(() => { if (packageInfo.name !== 'endpoint') { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.test.tsx index a2e9c7c7c55f..d8c320a3c101 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.test.tsx @@ -82,7 +82,23 @@ describe('useActionStatus', () => { }); expect(mockSendPostCancelAction).toHaveBeenCalledWith('action1'); expect(mockOnAbortSuccess).toHaveBeenCalled(); - expect(mockOpenConfirm).toHaveBeenCalledWith('This action will abort upgrade of 1 agents', { + expect(mockOpenConfirm).toHaveBeenCalledWith('This action will abort upgrade of 1 agent', { + title: 'Abort upgrade?', + }); + }); + + it('should post abort and invoke callback on abort upgrade - plural', async () => { + mockSendPostCancelAction.mockResolvedValue({}); + let result: any | undefined; + await act(async () => { + ({ result } = renderHook(() => useActionStatus(mockOnAbortSuccess, false))); + }); + await act(async () => { + await result.current.abortUpgrade({ ...mockActionStatuses[0], nbAgentsAck: 0 }); + }); + expect(mockSendPostCancelAction).toHaveBeenCalledWith('action1'); + expect(mockOnAbortSuccess).toHaveBeenCalled(); + expect(mockOpenConfirm).toHaveBeenCalledWith('This action will abort upgrade of 2 agents', { title: 'Abort upgrade?', }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.tsx index bc8e20b17ee0..3a6e1b2e5429 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.tsx @@ -54,7 +54,8 @@ export function useActionStatus(onAbortSuccess: () => void, refreshAgentActivity try { const confirmRes = await overlays.openConfirm( i18n.translate('xpack.fleet.currentUpgrade.confirmDescription', { - defaultMessage: 'This action will abort upgrade of {nbAgents} agents', + defaultMessage: + 'This action will abort upgrade of {nbAgents, plural, one {# agent} other {# agents}}', values: { nbAgents: action.nbAgentsActioned - action.nbAgentsAck, }, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.test.tsx index d5d72c3deaee..89059d5a1797 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.test.tsx @@ -45,7 +45,7 @@ describe('useUpdateTags', () => { await act(() => result.current.updateTags('agent1', ['tag1'], mockOnSuccess)); expect(mockOnSuccess).toHaveBeenCalled(); expect(useStartServices().notifications.toasts.addSuccess as jest.Mock).toHaveBeenCalledWith( - 'Tags updated' + 'Tag(s) updated' ); }); @@ -57,7 +57,7 @@ describe('useUpdateTags', () => { expect(mockOnSuccess).not.toHaveBeenCalled(); expect(useStartServices().notifications.toasts.addError as jest.Mock).toHaveBeenCalledWith( 'error', - { title: 'Tags update failed' } + { title: 'Tag(s) update failed' } ); }); @@ -68,7 +68,7 @@ describe('useUpdateTags', () => { await act(() => result.current.bulkUpdateTags('query', ['tag1'], [], mockOnSuccess)); expect(mockOnSuccess).toHaveBeenCalled(); expect(useStartServices().notifications.toasts.addSuccess as jest.Mock).toHaveBeenCalledWith( - 'Tags updated' + 'Tag(s) updated' ); }); @@ -80,7 +80,7 @@ describe('useUpdateTags', () => { expect(mockOnSuccess).not.toHaveBeenCalled(); expect(useStartServices().notifications.toasts.addError as jest.Mock).toHaveBeenCalledWith( 'error', - { title: 'Tags update failed' } + { title: 'Tag(s) update failed' } ); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.tsx index 969b9caa4d02..96e619db12f0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.tsx @@ -34,7 +34,7 @@ export const useUpdateTags = () => { const message = successMessage ?? i18n.translate('xpack.fleet.updateAgentTags.successNotificationTitle', { - defaultMessage: 'Tags updated', + defaultMessage: 'Tag(s) updated', }); notifications.toasts.addSuccess(message); @@ -43,7 +43,7 @@ export const useUpdateTags = () => { const errorTitle = errorMessage ?? i18n.translate('xpack.fleet.updateAgentTags.errorNotificationTitle', { - defaultMessage: 'Tags update failed', + defaultMessage: 'Tag(s) update failed', }); notifications.toasts.addError(error, { title: errorTitle }); } diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx index 58d3a847d3ef..7539197e8462 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx @@ -6,7 +6,7 @@ */ import { Search as LocalSearch, PrefixIndexStrategy } from 'js-search'; -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import type { IntegrationCardItem } from '../../../../common/types/models'; @@ -16,13 +16,11 @@ export const fieldsToSearch = ['name', 'title']; export function useLocalSearch(packageList: IntegrationCardItem[]) { const localSearchRef = useRef(new LocalSearch(searchIdField)); - useEffect(() => { - const localSearch = new LocalSearch(searchIdField); - localSearch.indexStrategy = new PrefixIndexStrategy(); - fieldsToSearch.forEach((field) => localSearch.addIndex(field)); - localSearch.addDocuments(packageList); - localSearchRef.current = localSearch; - }, [packageList]); + const localSearch = new LocalSearch(searchIdField); + localSearch.indexStrategy = new PrefixIndexStrategy(); + fieldsToSearch.forEach((field) => localSearch.addIndex(field)); + localSearch.addDocuments(packageList); + localSearchRef.current = localSearch; return localSearchRef; } diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts index 05e460e056c4..d137d908ca3b 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts @@ -8,6 +8,7 @@ import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common'; import type { IntegrationCategory } from '@kbn/custom-integrations-plugin/common'; +import { renderHook } from '@testing-library/react-hooks'; import type { PackageListItem } from '../../../../common/types/models'; @@ -46,8 +47,11 @@ describe('useMergeEprWithReplacements', () => { categories: ['cloud', 'datastore'], }, ]); + const { result } = renderHook(() => + useMergeEprPackagesWithReplacements(eprPackages, replacements) + ); - expect(useMergeEprPackagesWithReplacements(eprPackages, replacements)).toEqual([ + expect(result.current).toEqual([ { name: 'aws', release: 'ga', @@ -81,7 +85,10 @@ describe('useMergeEprWithReplacements', () => { }, ]); - expect(useMergeEprPackagesWithReplacements(eprPackages, replacements)).toEqual([ + const { result } = renderHook(() => + useMergeEprPackagesWithReplacements(eprPackages, replacements) + ); + expect(result.current).toEqual([ { eprOverlap: 'activemq', id: 'activemq-logs', @@ -109,7 +116,10 @@ describe('useMergeEprWithReplacements', () => { }, ]); - expect(useMergeEprPackagesWithReplacements(eprPackages, replacements)).toEqual([ + const { result } = renderHook(() => + useMergeEprPackagesWithReplacements(eprPackages, replacements) + ); + expect(result.current).toEqual([ { name: 'activemq', release: 'beta', @@ -129,7 +139,9 @@ describe('useMergeEprWithReplacements', () => { }, ]); - expect(useMergeEprPackagesWithReplacements(eprPackages, [])).toEqual([]); + const { result } = renderHook(() => useMergeEprPackagesWithReplacements(eprPackages, [])); + + expect(result.current).toEqual([]); }); test('should consists of all 3 types (ga eprs, replacements for non-ga eprs, replacements without epr equivalent', () => { @@ -180,7 +192,11 @@ describe('useMergeEprWithReplacements', () => { }, ]); - expect(useMergeEprPackagesWithReplacements(eprPackages, replacements)).toEqual([ + const { result } = renderHook(() => + useMergeEprPackagesWithReplacements(eprPackages, replacements) + ); + + expect(result.current).toEqual([ { name: 'aws', release: 'ga', diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.ts b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.ts index 412cdac83c4a..750985cb8038 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.ts @@ -8,6 +8,8 @@ import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common'; import { filterCustomIntegrations } from '@kbn/custom-integrations-plugin/public'; +import { useMemo } from 'react'; + import type { PackageListItem } from '../../../../common/types/models'; import { FLEET_APM_PACKAGE } from '../../../../common/constants'; @@ -27,44 +29,46 @@ export function useMergeEprPackagesWithReplacements( rawEprPackages: PackageListItem[], replacements: CustomIntegration[] ): Array { - const merged: Array = []; - const filteredReplacements = replacements; + return useMemo(() => { + const merged: Array = []; + const filteredReplacements = replacements; - // APM EPR-packages should _never_ show. They have special handling. - const eprPackages = rawEprPackages.filter((p) => { - return p.name !== FLEET_APM_PACKAGE; - }); + // APM EPR-packages should _never_ show. They have special handling. + const eprPackages = rawEprPackages.filter((p) => { + return p.name !== FLEET_APM_PACKAGE; + }); - // Either select replacement or select beat - eprPackages.forEach((eprPackage: PackageListItem) => { - const hits = findReplacementsForEprPackage( - filteredReplacements, - eprPackage.name, - eprPackage.release - ); - if (hits.length) { - hits.forEach((hit) => { - const match = merged.find(({ id }) => { - return id === hit.id; + // Either select replacement or select beat + eprPackages.forEach((eprPackage: PackageListItem) => { + const hits = findReplacementsForEprPackage( + filteredReplacements, + eprPackage.name, + eprPackage.release + ); + if (hits.length) { + hits.forEach((hit) => { + const match = merged.find(({ id }) => { + return id === hit.id; + }); + if (!match) { + merged.push(hit); + } }); - if (!match) { - merged.push(hit); - } - }); - } else { - merged.push(eprPackage); - } - }); + } else { + merged.push(eprPackage); + } + }); - // Add unused replacements - // This is an edge-case. E.g. the Oracle-beat did not have an Epr-equivalent at the time of writing - const unusedReplacements = filteredReplacements.filter((integration) => { - return !eprPackages.find((eprPackage) => { - return eprPackage.name === integration.eprOverlap; + // Add unused replacements + // This is an edge-case. E.g. the Oracle-beat did not have an Epr-equivalent at the time of writing + const unusedReplacements = filteredReplacements.filter((integration) => { + return !eprPackages.find((eprPackage) => { + return eprPackage.name === integration.eprOverlap; + }); }); - }); - merged.push(...unusedReplacements); + merged.push(...unusedReplacements); - return merged; + return merged; + }, [rawEprPackages, replacements]); } diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx index e360f615ad36..a3495d599870 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx @@ -6,6 +6,7 @@ */ import type { ReactNode, FunctionComponent } from 'react'; +import { useMemo } from 'react'; import React, { useCallback, useState, useRef, useEffect } from 'react'; import { @@ -98,19 +99,23 @@ export const PackageListGrid: FunctionComponent = ({ ? categories.find((category) => category.id === selectedCategory)?.title : undefined; - const controlsContent = ; - let gridContent: JSX.Element; - - if (isLoading || !localSearchRef.current) { - gridContent = ; - } else { - const filteredList = searchTerm + const filteredList = useMemo(() => { + if (isLoading) return []; + return searchTerm ? list.filter((item) => (localSearchRef.current!.search(searchTerm) as IntegrationCardItem[]) .map((match) => match[searchIdField]) .includes(item[searchIdField]) ) : list; + }, [isLoading, list, localSearchRef, searchTerm]); + + const controlsContent = ; + let gridContent: JSX.Element; + + if (isLoading || !localSearchRef.current) { + gridContent = ; + } else { gridContent = ( { + if (packageKey.startsWith('endpoint') && tourType === 'addIntegrationButton') { + return { + title: i18n.translate('xpack.fleet.guidedOnboardingTour.endpointButton.title', { + defaultMessage: 'Add Elastic Defend', + }), + description: i18n.translate('xpack.fleet.guidedOnboardingTour.endpointButton.description', { + defaultMessage: + 'In just a few steps, configure your data with our recommended defaults. You can change this later.', + }), + }; + } + return null; +}; +export const WithGuidedOnboardingTour: FunctionComponent<{ + packageKey: string; + isGuidedOnboardingActive: boolean; + tourType: TourType; + children: ReactElement; +}> = ({ packageKey, isGuidedOnboardingActive, tourType, children }) => { + const [isGuidedOnboardingTourOpen, setIsGuidedOnboardingTourOpen] = + useState(isGuidedOnboardingActive); + useEffect(() => { + setIsGuidedOnboardingTourOpen(isGuidedOnboardingActive); + }, [isGuidedOnboardingActive]); + const config = getTourConfig(packageKey, tourType); + + return config ? ( + {config.description}} + isStepOpen={isGuidedOnboardingTourOpen} + maxWidth={350} + onFinish={() => setIsGuidedOnboardingTourOpen(false)} + step={1} + stepsTotal={1} + title={config.title} + anchorPosition="rightUp" + footerAction={ + setIsGuidedOnboardingTourOpen(false)} size="s" color="success"> + {i18n.translate('xpack.fleet.guidedOnboardingTour.nextButtonLabel', { + defaultMessage: 'Next', + })} + + } + > + {children} + + ) : ( + <>{children} + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index d43afbb28835..caefad75ad7a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -39,7 +39,12 @@ import { } from '../../../../hooks'; import { INTEGRATIONS_ROUTING_PATHS } from '../../../../constants'; import { ExperimentalFeaturesService } from '../../../../services'; -import { useGetPackageInfoByKey, useLink, useAgentPolicyContext } from '../../../../hooks'; +import { + useGetPackageInfoByKey, + useLink, + useAgentPolicyContext, + useIsGuidedOnboardingActive, +} from '../../../../hooks'; import { pkgKeyFromPackageInfo } from '../../../../services'; import type { DetailViewPanelName, PackageInfo } from '../../../../types'; import { InstallStatus } from '../../../../types'; @@ -47,6 +52,8 @@ import { Error, Loading, HeaderReleaseBadge } from '../../../../components'; import type { WithHeaderLayoutProps } from '../../../../layouts'; import { WithHeaderLayout } from '../../../../layouts'; +import { WithGuidedOnboardingTour } from './components/with_guided_onboarding_tour'; + import { useIsFirstTimeAgentUser } from './hooks'; import { getInstallPkgRouteOptions } from './utils'; import { @@ -154,6 +161,7 @@ export function Detail() { const { isFirstTimeAgentUser = false, isLoading: firstTimeUserLoading } = useIsFirstTimeAgentUser(); + const isGuidedOnboardingActive = useIsGuidedOnboardingActive(pkgName); // Refresh package info when status change const [oldPackageInstallStatus, setOldPackageStatus] = useState(packageInstallStatus); @@ -292,6 +300,7 @@ export function Detail() { isCloud, isExperimentalAddIntegrationPageEnabled, isFirstTimeAgentUser, + isGuidedOnboardingActive, pkgkey, }); @@ -305,6 +314,7 @@ export function Detail() { isCloud, isExperimentalAddIntegrationPageEnabled, isFirstTimeAgentUser, + isGuidedOnboardingActive, pathname, pkgkey, search, @@ -349,19 +359,25 @@ export function Detail() { { isDivider: true }, { content: ( - + + + ), }, ].map((item, index) => ( @@ -385,6 +401,7 @@ export function Detail() { packageInfo, updateAvailable, isInstalled, + isGuidedOnboardingActive, userCanInstallPackages, getHref, pkgkey, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts index e085b9034235..7d233f0977b8 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts @@ -22,6 +22,7 @@ describe('getInstallPkgRouteOptions', () => { integration: 'myintegration', pkgkey: 'myintegration-1.0.0', isFirstTimeAgentUser: false, + isGuidedOnboardingActive: false, isCloud: false, isExperimentalAddIntegrationPageEnabled: false, }; @@ -51,6 +52,7 @@ describe('getInstallPkgRouteOptions', () => { pkgkey: 'myintegration-1.0.0', agentPolicyId: '12345', isFirstTimeAgentUser: false, + isGuidedOnboardingActive: false, isCloud: false, isExperimentalAddIntegrationPageEnabled: false, }; @@ -78,6 +80,7 @@ describe('getInstallPkgRouteOptions', () => { integration: 'myintegration', pkgkey: 'myintegration-1.0.0', isFirstTimeAgentUser: true, + isGuidedOnboardingActive: false, isCloud: true, isExperimentalAddIntegrationPageEnabled: true, }; @@ -105,6 +108,7 @@ describe('getInstallPkgRouteOptions', () => { integration: 'myintegration', pkgkey: 'apm-1.0.0', isFirstTimeAgentUser: true, + isGuidedOnboardingActive: false, isCloud: true, isExperimentalAddIntegrationPageEnabled: true, }; @@ -137,6 +141,7 @@ describe('getInstallPkgRouteOptions', () => { integration: 'myintegration', pkgkey: 'endpoint-1.0.0', isFirstTimeAgentUser: true, + isGuidedOnboardingActive: false, isCloud: true, isExperimentalAddIntegrationPageEnabled: true, }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts index 6a8612a44f42..f4ac18057dbb 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts @@ -29,6 +29,7 @@ interface GetInstallPkgRouteOptionsParams { isCloud: boolean; isExperimentalAddIntegrationPageEnabled: boolean; isFirstTimeAgentUser: boolean; + isGuidedOnboardingActive: boolean; } const isPackageExemptFromStepsLayout = (pkgkey: string) => @@ -45,13 +46,14 @@ export const getInstallPkgRouteOptions = ({ isFirstTimeAgentUser, isCloud, isExperimentalAddIntegrationPageEnabled, + isGuidedOnboardingActive, }: GetInstallPkgRouteOptionsParams): [string, { path: string; state: unknown }] => { const integrationOpts: { integration?: string } = integration ? { integration } : {}; const packageExemptFromStepsLayout = isPackageExemptFromStepsLayout(pkgkey); const useMultiPageLayout = isExperimentalAddIntegrationPageEnabled && isCloud && - isFirstTimeAgentUser && + (isFirstTimeAgentUser || isGuidedOnboardingActive) && !packageExemptFromStepsLayout; const path = pagePathGetters.add_integration_to_policy({ pkgkey, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/available_packages.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/available_packages.tsx index c9a354df7485..b85b68d0114d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/available_packages.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/available_packages.tsx @@ -253,27 +253,35 @@ export const AvailablePackages: React.FC<{ ); const { value: replacementCustomIntegrations } = useGetReplacementCustomIntegrations(); + const { loading: isLoadingAppendCustomIntegrations, value: appendCustomIntegrations } = + useGetAppendCustomIntegrations(); + const mergedEprPackages: Array = useMergeEprPackagesWithReplacements( preference === 'beats' ? [] : eprIntegrationList, preference === 'agent' ? [] : replacementCustomIntegrations || [] ); - - const { loading: isLoadingAppendCustomIntegrations, value: appendCustomIntegrations } = - useGetAppendCustomIntegrations(); - - const eprAndCustomPackages: Array = [ - ...mergedEprPackages, - ...(appendCustomIntegrations || []), - ]; - - const cards: IntegrationCardItem[] = eprAndCustomPackages.map((item) => { - return mapToCard({ getAbsolutePath, getHref, item, addBasePath }); - }); - - cards.sort((a, b) => { - return a.title.localeCompare(b.title); - }); + const cards: IntegrationCardItem[] = useMemo(() => { + const eprAndCustomPackages = [...mergedEprPackages, ...(appendCustomIntegrations || [])]; + + return eprAndCustomPackages + .map((item) => { + return mapToCard({ getAbsolutePath, getHref, item, addBasePath }); + }) + .sort((a, b) => a.title.localeCompare(b.title)); + }, [addBasePath, appendCustomIntegrations, getAbsolutePath, getHref, mergedEprPackages]); + + const filteredCards = useMemo( + () => + cards.filter((c) => { + if (category === '') { + return true; + } + + return c.categories.includes(category); + }), + [cards, category] + ); const { data: eprCategories, @@ -331,14 +339,6 @@ export const AvailablePackages: React.FC<{ ]; } - const filteredCards = cards.filter((c) => { - if (category === '') { - return true; - } - - return c.categories.includes(category); - }); - // TODO: Remove this hard coded list of integrations with a suggestion service const featuredList = ( <> diff --git a/x-pack/plugins/fleet/public/hooks/index.ts b/x-pack/plugins/fleet/public/hooks/index.ts index 579d1ab5bc3d..b155ccf63a0d 100644 --- a/x-pack/plugins/fleet/public/hooks/index.ts +++ b/x-pack/plugins/fleet/public/hooks/index.ts @@ -28,3 +28,4 @@ export * from './use_agent_policy_refresh'; export * from './use_package_installations'; export * from './use_agent_enrollment_flyout_data'; export * from './use_flyout_context'; +export * from './use_is_guided_onboarding_active'; diff --git a/x-pack/plugins/fleet/public/hooks/use_is_guided_onboarding_active.ts b/x-pack/plugins/fleet/public/hooks/use_is_guided_onboarding_active.ts new file mode 100644 index 000000000000..22b8ab9b1a23 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_is_guided_onboarding_active.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; + +import { of } from 'rxjs'; + +import { useStartServices } from '.'; + +export const useIsGuidedOnboardingActive = (packageName?: string): boolean => { + const [result, setResult] = useState(false); + const { guidedOnboarding } = useStartServices(); + const isGuidedOnboardingActiveForIntegration = useObservable( + // if guided onboarding is not available, return false + guidedOnboarding.guidedOnboardingApi + ? guidedOnboarding.guidedOnboardingApi.isGuidedOnboardingActiveForIntegration$(packageName) + : of(false) + ); + useEffect(() => { + setResult(!!isGuidedOnboardingActiveForIntegration); + }, [isGuidedOnboardingActiveForIntegration]); + + return result; +}; diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index 823d46becb4f..7b53d4e1d2cc 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -37,7 +37,6 @@ export type { PackagePolicyCreateExtensionComponentProps, PackagePolicyCreateMultiStepExtension, PackagePolicyCreateMultiStepExtensionComponent, - PackagePolicyCreateMultiStepExtensionComponentProps, PackagePolicyEditExtension, PackagePolicyEditExtensionComponent, PackagePolicyEditExtensionComponentProps, diff --git a/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx b/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx index c5d9b5011156..86816e296dde 100644 --- a/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx +++ b/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx @@ -13,6 +13,8 @@ import { coreMock } from '@kbn/core/public/mocks'; import type { IStorage } from '@kbn/kibana-utils-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks'; + import { setHttpClient } from '../hooks/use_request'; import type { FleetAuthz } from '../../common'; @@ -90,6 +92,7 @@ export const createStartServices = (basePath: string = '/mock'): MockedFleetStar }, storage: new Storage(createMockStore()) as jest.Mocked, authz: fleetAuthzMock, + guidedOnboarding: guidedOnboardingMock.createStart(), }; configureStartServices(startServices); diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index b1d845aa9e52..3590a80037b1 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -44,6 +44,7 @@ import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; import { PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, setupRouteService, appRoutesService } from '../common'; import { calculateAuthz, calculatePackagePrivilegesFromCapabilities } from '../common/authz'; @@ -101,6 +102,7 @@ export interface FleetStartDeps { share: SharePluginStart; cloud?: CloudStart; usageCollection?: UsageCollectionStart; + guidedOnboarding: GuidedOnboardingPluginStart; } export interface FleetStartServices extends CoreStart, Exclude { @@ -110,6 +112,7 @@ export interface FleetStartServices extends CoreStart, Exclude { diff --git a/x-pack/plugins/fleet/public/types/ui_extensions.ts b/x-pack/plugins/fleet/public/types/ui_extensions.ts index 24f9fef73061..fc8726e7b813 100644 --- a/x-pack/plugins/fleet/public/types/ui_extensions.ts +++ b/x-pack/plugins/fleet/public/types/ui_extensions.ts @@ -136,13 +136,7 @@ export interface PackagePolicyCreateExtension { * UI Component Extension is used on the pages displaying the ability to Create a multi step * Integration Policy */ -export type PackagePolicyCreateMultiStepExtensionComponent = - ComponentType; - -export interface PackagePolicyCreateMultiStepExtensionComponentProps { - /** The integration policy being created */ - newPolicy: NewPackagePolicy; -} +export type PackagePolicyCreateMultiStepExtensionComponent = ComponentType<{}>; /** Extension point registration contract for Integration Policy Create views in multi-step onboarding */ export interface PackagePolicyCreateMultiStepExtension { diff --git a/x-pack/plugins/fleet/server/config.ts b/x-pack/plugins/fleet/server/config.ts index 48641d49f6e5..0e685e8b4513 100644 --- a/x-pack/plugins/fleet/server/config.ts +++ b/x-pack/plugins/fleet/server/config.ts @@ -21,6 +21,7 @@ import { PreconfiguredPackagesSchema, PreconfiguredAgentPoliciesSchema, PreconfiguredOutputsSchema, + PreconfiguredFleetServerHostsSchema, } from './types'; const DEFAULT_BUNDLED_PACKAGE_LOCATION = path.join(__dirname, '../target/bundled_packages'); @@ -115,6 +116,7 @@ export const config: PluginConfigDescriptor = { packages: PreconfiguredPackagesSchema, agentPolicies: PreconfiguredAgentPoliciesSchema, outputs: PreconfiguredOutputsSchema, + fleetServerHosts: PreconfiguredFleetServerHostsSchema, agentIdVerificationEnabled: schema.boolean({ defaultValue: true }), developer: schema.object({ disableRegistryVersionCheck: schema.boolean({ defaultValue: false }), diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index cd0c0262a77d..33004fe3030c 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -64,6 +64,9 @@ export { DEFAULT_DOWNLOAD_SOURCE_URI, DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, DEFAULT_DOWNLOAD_SOURCE_ID, + // Fleet server host + DEFAULT_FLEET_SERVER_HOST_ID, + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, // Authz ENDPOINT_PRIVILEGES, } from '../../common/constants'; diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index 1b8a627a3572..d899f96886bc 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -76,6 +76,8 @@ export class OutputInvalidError extends FleetError {} export class OutputLicenceError extends FleetError {} export class DownloadSourceError extends FleetError {} +export class FleetServerHostUnauthorizedError extends FleetError {} + export class ArtifactsClientError extends FleetError {} export class ArtifactsClientAccessDeniedError extends FleetError { constructor(deniedPackageName: string, allowedPackageName: string) { diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index bbd55a517753..9e02ad3f9640 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -64,6 +64,7 @@ import { ASSETS_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, } from './constants'; import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects'; import { @@ -80,6 +81,7 @@ import { registerPreconfigurationRoutes, registerDownloadSourcesRoutes, registerHealthCheckRoutes, + registerFleetServerHostRoutes, } from './routes'; import type { ExternalCallback, FleetRequestHandlerContext } from './types'; @@ -161,6 +163,7 @@ const allSavedObjectTypes = [ ASSETS_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, ]; /** @@ -399,6 +402,7 @@ export class FleetPlugin registerSettingsRoutes(fleetAuthzRouter); registerDataStreamRoutes(fleetAuthzRouter); registerPreconfigurationRoutes(fleetAuthzRouter); + registerFleetServerHostRoutes(fleetAuthzRouter); registerDownloadSourcesRoutes(fleetAuthzRouter); registerHealthCheckRoutes(fleetAuthzRouter); diff --git a/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/handler.ts b/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/handler.ts new file mode 100644 index 000000000000..3df4daf12611 --- /dev/null +++ b/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/handler.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TypeOf } from '@kbn/config-schema'; +import type { RequestHandler } from '@kbn/core/server'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; + +import { defaultFleetErrorHandler } from '../../errors'; +import { agentPolicyService } from '../../services'; +import { + createFleetServerHost, + deleteFleetServerHost, + getFleetServerHost, + listFleetServerHosts, + updateFleetServerHost, +} from '../../services/fleet_server_host'; +import type { + GetOneFleetServerHostRequestSchema, + PostFleetServerHostRequestSchema, + PutFleetServerHostRequestSchema, +} from '../../types'; + +export const postFleetServerHost: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + const coreContext = await context.core; + const soClient = coreContext.savedObjects.client; + const esClient = coreContext.elasticsearch.client.asInternalUser; + try { + const { id, ...data } = request.body; + const FleetServerHost = await createFleetServerHost( + soClient, + { ...data, is_preconfigured: false }, + { id } + ); + if (FleetServerHost.is_default) { + await agentPolicyService.bumpAllAgentPolicies(soClient, esClient); + } + + const body = { + item: FleetServerHost, + }; + + return response.ok({ body }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; + +export const getFleetServerPolicyHandler: RequestHandler< + TypeOf +> = async (context, request, response) => { + const soClient = (await context.core).savedObjects.client; + try { + const item = await getFleetServerHost(soClient, request.params.itemId); + const body = { + item, + }; + + return response.ok({ body }); + } catch (error) { + if (SavedObjectsErrorHelpers.isNotFoundError(error)) { + return response.notFound({ + body: { message: `Fleet server ${request.params.itemId} not found` }, + }); + } + + return defaultFleetErrorHandler({ error, response }); + } +}; + +export const deleteFleetServerPolicyHandler: RequestHandler< + TypeOf +> = async (context, request, response) => { + const soClient = (await context.core).savedObjects.client; + try { + await deleteFleetServerHost(soClient, request.params.itemId); + const body = { + id: request.params.itemId, + }; + + return response.ok({ body }); + } catch (error) { + if (SavedObjectsErrorHelpers.isNotFoundError(error)) { + return response.notFound({ + body: { message: `Fleet server ${request.params.itemId} not found` }, + }); + } + + return defaultFleetErrorHandler({ error, response }); + } +}; + +export const putFleetServerPolicyHandler: RequestHandler< + TypeOf, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = (await context.core).savedObjects.client; + try { + const item = await updateFleetServerHost(soClient, request.params.itemId, request.body); + const body = { + item, + }; + + return response.ok({ body }); + } catch (error) { + if (SavedObjectsErrorHelpers.isNotFoundError(error)) { + return response.notFound({ + body: { message: `Fleet server ${request.params.itemId} not found` }, + }); + } + + return defaultFleetErrorHandler({ error, response }); + } +}; + +export const getAllFleetServerPolicyHandler: RequestHandler< + TypeOf, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = (await context.core).savedObjects.client; + try { + const res = await listFleetServerHosts(soClient); + const body = { + items: res.items, + page: res.page, + perPage: res.perPage, + total: res.total, + }; + + return response.ok({ body }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/index.ts b/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/index.ts new file mode 100644 index 000000000000..48607c5df7a7 --- /dev/null +++ b/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/index.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FLEET_SERVER_HOST_API_ROUTES } from '../../../common/constants'; +import { + GetAllFleetServerHostRequestSchema, + GetOneFleetServerHostRequestSchema, + PostFleetServerHostRequestSchema, + PutFleetServerHostRequestSchema, +} from '../../types'; + +import type { FleetAuthzRouter } from '../security'; + +import { + deleteFleetServerPolicyHandler, + getAllFleetServerPolicyHandler, + getFleetServerPolicyHandler, + postFleetServerHost, + putFleetServerPolicyHandler, +} from './handler'; + +export const registerRoutes = (router: FleetAuthzRouter) => { + router.get( + { + path: FLEET_SERVER_HOST_API_ROUTES.LIST_PATTERN, + validate: GetAllFleetServerHostRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + getAllFleetServerPolicyHandler + ); + router.post( + { + path: FLEET_SERVER_HOST_API_ROUTES.CREATE_PATTERN, + validate: PostFleetServerHostRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + postFleetServerHost + ); + router.get( + { + path: FLEET_SERVER_HOST_API_ROUTES.INFO_PATTERN, + validate: GetOneFleetServerHostRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + getFleetServerPolicyHandler + ); + router.delete( + { + path: FLEET_SERVER_HOST_API_ROUTES.DELETE_PATTERN, + validate: GetOneFleetServerHostRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + deleteFleetServerPolicyHandler + ); + router.put( + { + path: FLEET_SERVER_HOST_API_ROUTES.UPDATE_PATTERN, + validate: PutFleetServerHostRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + putFleetServerPolicyHandler + ); +}; diff --git a/x-pack/plugins/fleet/server/routes/index.ts b/x-pack/plugins/fleet/server/routes/index.ts index 24c0947a419f..7dd720f5d267 100644 --- a/x-pack/plugins/fleet/server/routes/index.ts +++ b/x-pack/plugins/fleet/server/routes/index.ts @@ -18,3 +18,4 @@ export { registerRoutes as registerAppRoutes } from './app'; export { registerRoutes as registerPreconfigurationRoutes } from './preconfiguration'; export { registerRoutes as registerDownloadSourcesRoutes } from './download_source'; export { registerRoutes as registerHealthCheckRoutes } from './health_check'; +export { registerRoutes as registerFleetServerHostRoutes } from './fleet_server_policy_config'; diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index ed03e26b6453..4a943789cac6 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -18,6 +18,7 @@ import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, } from '../constants'; import { @@ -344,6 +345,22 @@ const getSavedObjectTypes = ( }, }, }, + [FLEET_SERVER_HOST_SAVED_OBJECT_TYPE]: { + name: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + name: { type: 'keyword' }, + is_default: { type: 'boolean' }, + host_urls: { type: 'keyword', index: false }, + is_preconfigured: { type: 'boolean' }, + }, + }, + }, }); export function registerSavedObjects( diff --git a/x-pack/plugins/fleet/server/services/agents/action_status.ts b/x-pack/plugins/fleet/server/services/agents/action_status.ts index a057af185a06..7ab162b0aef7 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_status.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_status.ts @@ -64,12 +64,13 @@ export async function getActionStatuses( const matchingBucket = (acks?.aggregations?.ack_counts as any)?.buckets?.find( (bucket: any) => bucket.key === action.actionId ); + const nbAgentsActioned = action.nbAgentsActioned || action.nbAgentsActionCreated; const nbAgentsAck = Math.min( matchingBucket?.doc_count ?? 0, - (matchingBucket?.agent_count as any)?.value ?? 0 + (matchingBucket?.agent_count as any)?.value ?? 0, + nbAgentsActioned ); const completionTime = (matchingBucket?.max_timestamp as any)?.value_as_string; - const nbAgentsActioned = action.nbAgentsActioned || action.nbAgentsActionCreated; const complete = nbAgentsAck >= nbAgentsActioned; const cancelledAction = cancelledActions.find((a) => a.actionId === action.actionId); diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts index 9169df19fbcf..b1e69fa86148 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts @@ -331,15 +331,22 @@ describe('invalidateAPIKeysForAgents', () => { id: 'defaultApiKeyHistory2', }, ], - outputs: [ - { + outputs: { + output1: { api_key_id: 'outputApiKey1', to_retire_api_key_ids: [{ id: 'outputApiKeyRetire1' }, { id: 'outputApiKeyRetire2' }], }, - { + output2: { api_key_id: 'outputApiKey2', }, - ], + output3: { + api_key_id: 'outputApiKey3', + to_retire_api_key_ids: [ + // Somes Fleet Server agents don't have an id here (probably a bug) + { retired_at: 'foo' }, + ], + }, + }, } as any, ]); @@ -353,6 +360,7 @@ describe('invalidateAPIKeysForAgents', () => { 'outputApiKeyRetire1', 'outputApiKeyRetire2', 'outputApiKey2', + 'outputApiKey3', ]); }); }); diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts index fed5d44fe98e..1b17e688a8a2 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts @@ -216,12 +216,16 @@ export async function invalidateAPIKeysForAgents(agents: Agent[]) { agent.default_api_key_history.forEach((apiKey) => keys.push(apiKey.id)); } if (agent.outputs) { - agent.outputs.forEach((output) => { + Object.values(agent.outputs).forEach((output) => { if (output.api_key_id) { keys.push(output.api_key_id); } if (output.to_retire_api_key_ids) { - output.to_retire_api_key_ids.forEach((apiKey) => keys.push(apiKey.id)); + Object.values(output.to_retire_api_key_ids).forEach((apiKey) => { + if (apiKey?.id) { + keys.push(apiKey.id); + } + }); } }); } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts index 8c908ecc9ef8..4999c07a2aec 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts @@ -261,9 +261,10 @@ const installTransformsAssets = async ( await Promise.all( destinationIndexTemplates .map((destinationIndexTemplate) => { - const customMappings = transformsSpecifications - .get(destinationIndexTemplate.transformModuleId) - ?.get('mappings'); + const customMappings = + transformsSpecifications + .get(destinationIndexTemplate.transformModuleId) + ?.get('mappings') ?? {}; const registryElasticsearch: RegistryElasticsearch = { 'index_template.settings': destinationIndexTemplate.template.settings, 'index_template.mappings': destinationIndexTemplate.template.mappings, diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/legacy_transforms.test.ts similarity index 99% rename from x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/legacy_transforms.test.ts index 97fa1e94ca21..124004dee94a 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/legacy_transforms.test.ts @@ -37,7 +37,7 @@ import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../constants'; import { getAsset } from './common'; import { installTransforms } from './install'; -describe('test transform install', () => { +describe('test transform install with legacy schema', () => { let esClient: ReturnType; let savedObjectsClient: jest.Mocked; beforeEach(() => { diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts new file mode 100644 index 000000000000..aeeeb59e12b3 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts @@ -0,0 +1,672 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// eslint-disable-next-line import/order +import { createAppContextStartContractMock } from '../../../../mocks'; + +jest.mock('../../packages/get', () => { + return { getInstallation: jest.fn(), getInstallationObject: jest.fn() }; +}); + +jest.mock('./common', () => { + return { + getAsset: jest.fn(), + }; +}); + +import type { SavedObject, SavedObjectsClientContract } from '@kbn/core/server'; +import { loggerMock } from '@kbn/logging-mocks'; + +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; + +import { getInstallation, getInstallationObject } from '../../packages'; +import type { Installation, RegistryPackage } from '../../../../types'; +import { ElasticsearchAssetType } from '../../../../types'; +import { appContextService } from '../../../app_context'; + +import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../constants'; + +import { getESAssetMetadata } from '../meta'; + +import { installTransforms } from './install'; +import { getAsset } from './common'; + +const meta = getESAssetMetadata({ packageName: 'endpoint' }); + +describe('test transform install', () => { + let esClient: ReturnType; + let savedObjectsClient: jest.Mocked; + + const getYamlTestData = (autoStart: boolean | undefined = undefined) => { + const start = + autoStart === undefined + ? '' + : ` +start: ${autoStart}`; + return { + MANIFEST: + `destination_index_template: + settings: + index: + codec: best_compression + refresh_interval: 5s + number_of_shards: 1 + number_of_routing_shards: 30 + hidden: true + mappings: + dynamic: false + _meta: {} + dynamic_templates: + - strings_as_keyword: + match_mapping_type: string + mapping: + ignore_above: 1024 + type: keyword + date_detection: false` + start, + TRANSFORM: `source: + index: + - metrics-endpoint.metadata_current_default* + - ".fleet-agents*" +dest: + index: ".metrics-endpoint.metadata_united_default" +frequency: 1s +sync: + time: + delay: 4s + field: updated_at +pivot: + aggs: + united: + scripted_metric: + init_script: state.docs = [] + map_script: state.docs.add(new HashMap(params['_source'])) + combine_script: return state.docs + reduce_script: def ret = new HashMap(); for (s in states) { for (d in s) { if (d.containsKey('Endpoint')) { ret.endpoint = d } else { ret.agent = d } }} return ret + group_by: + agent.id: + terms: + field: agent.id +description: Merges latest endpoint and Agent metadata documents. +_meta: + managed: true`, + FIELDS: `- name: '@timestamp' + type: date +- name: updated_at + type: alias + path: event.ingested`, + }; + }; + const getExpectedData = () => { + return { + TRANSFORM: { + transform_id: 'logs-endpoint.metadata_current-default-0.16.0-dev.0', + defer_validation: true, + body: { + description: 'Merges latest endpoint and Agent metadata documents.', + dest: { + index: '.metrics-endpoint.metadata_united_default', + }, + frequency: '1s', + pivot: { + aggs: { + united: { + scripted_metric: { + combine_script: 'return state.docs', + init_script: 'state.docs = []', + map_script: "state.docs.add(new HashMap(params['_source']))", + reduce_script: + "def ret = new HashMap(); for (s in states) { for (d in s) { if (d.containsKey('Endpoint')) { ret.endpoint = d } else { ret.agent = d } }} return ret", + }, + }, + }, + group_by: { + 'agent.id': { + terms: { + field: 'agent.id', + }, + }, + }, + }, + source: { + index: ['metrics-endpoint.metadata_current_default*', '.fleet-agents*'], + }, + sync: { + time: { + delay: '4s', + field: 'updated_at', + }, + }, + _meta: meta, + }, + }, + }; + }; + + beforeEach(() => { + appContextService.start(createAppContextStartContractMock()); + esClient = elasticsearchClientMock.createClusterClient().asInternalUser; + (getInstallation as jest.MockedFunction).mockReset(); + (getInstallationObject as jest.MockedFunction).mockReset(); + savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.update.mockImplementation(async (type, id, attributes) => ({ + type: PACKAGES_SAVED_OBJECT_TYPE, + id: 'endpoint', + attributes, + references: [], + })); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('can install new versions and removes older version when start is not defined', async () => { + const sourceData = getYamlTestData(); + const expectedData = getExpectedData(); + + const previousInstallation: Installation = { + installed_es: [ + { + id: 'metrics-endpoint.policy-0.16.0-dev.0', + type: ElasticsearchAssetType.ingestPipeline, + }, + { + id: 'endpoint.metadata_current-default-0.15.0-dev.0', + type: ElasticsearchAssetType.transform, + }, + ], + } as unknown as Installation; + + const currentInstallation: Installation = { + installed_es: [ + { + id: 'metrics-endpoint.policy-0.16.0-dev.0', + type: ElasticsearchAssetType.ingestPipeline, + }, + { + id: 'endpoint.metadata_current-default-0.15.0-dev.0', + type: ElasticsearchAssetType.transform, + }, + { + id: 'endpoint.metadata_current-default-0.16.0-dev.0', + type: ElasticsearchAssetType.transform, + }, + { + id: 'endpoint.metadata-default-0.16.0-dev.0', + type: ElasticsearchAssetType.transform, + }, + ], + } as unknown as Installation; + (getAsset as jest.MockedFunction) + .mockReturnValueOnce(Buffer.from(sourceData.MANIFEST, 'utf8')) + .mockReturnValueOnce(Buffer.from(sourceData.TRANSFORM, 'utf8')); + + (getInstallation as jest.MockedFunction) + .mockReturnValueOnce(Promise.resolve(previousInstallation)) + .mockReturnValueOnce(Promise.resolve(currentInstallation)); + + ( + getInstallationObject as jest.MockedFunction + ).mockReturnValueOnce( + Promise.resolve({ + attributes: { + installed_es: previousInstallation.installed_es, + }, + } as unknown as SavedObject) + ); + + // Mock transform from old version + esClient.transform.getTransform.mockResponseOnce({ + count: 1, + transforms: [ + // @ts-expect-error incomplete data + { + dest: { + index: 'mock-old-destination-index', + }, + }, + ], + }); + + await installTransforms( + { + name: 'endpoint', + version: '0.16.0-dev.0', + } as unknown as RegistryPackage, + [ + 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/manifest.yml', + 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/transform.yml', + ], + esClient, + savedObjectsClient, + loggerMock.create(), + previousInstallation.installed_es + ); + + expect(esClient.transform.getTransform.mock.calls).toEqual([ + [ + { + transform_id: 'endpoint.metadata_current-default-0.15.0-dev.0', + }, + { ignore: [404] }, + ], + ]); + // Stop and delete previously installed transforms + expect(esClient.transform.stopTransform.mock.calls).toEqual([ + [ + { + transform_id: 'endpoint.metadata_current-default-0.15.0-dev.0', + force: true, + }, + { ignore: [404] }, + ], + ]); + expect(esClient.transform.deleteTransform.mock.calls).toEqual([ + [ + { + transform_id: 'endpoint.metadata_current-default-0.15.0-dev.0', + force: true, + }, + { ignore: [404] }, + ], + ]); + + // Delete destination index + expect(esClient.transport.request.mock.calls).toEqual([ + [ + { + method: 'DELETE', + path: '/mock-old-destination-index', + }, + { ignore: [404] }, + ], + ]); + + // Create a @package component template and an empty @custom component template + expect(esClient.cluster.putComponentTemplate.mock.calls).toEqual([ + [ + { + body: { + _meta: meta, + template: { + mappings: { + _meta: {}, + date_detection: false, + dynamic: false, + dynamic_templates: [ + { + strings_as_keyword: { + mapping: { ignore_above: 1024, type: 'keyword' }, + match_mapping_type: 'string', + }, + }, + ], + properties: {}, + }, + settings: { + index: { + codec: 'best_compression', + hidden: true, + mapping: { total_fields: { limit: '10000' } }, + number_of_routing_shards: 30, + number_of_shards: 1, + refresh_interval: '5s', + }, + }, + }, + }, + create: false, + name: 'logs-endpoint.metadata_current-template@package', + }, + { ignore: [404] }, + ], + [ + { + body: { + _meta: meta, + template: { settings: {} }, + }, + create: true, + name: 'logs-endpoint.metadata_current-template@custom', + }, + { ignore: [404] }, + ], + ]); + + // Index template composed of the two component templates created + // with index pattern matching the destination index + expect(esClient.indices.putIndexTemplate.mock.calls).toEqual([ + [ + { + body: { + _meta: meta, + composed_of: [ + 'logs-endpoint.metadata_current-template@package', + 'logs-endpoint.metadata_current-template@custom', + ], + index_patterns: ['.metrics-endpoint.metadata_united_default'], + priority: 250, + template: { mappings: undefined, settings: undefined }, + }, + name: 'logs-endpoint.metadata_current-template', + }, + { ignore: [404] }, + ], + ]); + + // Destination index is created before transform is created + expect(esClient.indices.create.mock.calls).toEqual([ + [{ index: '.metrics-endpoint.metadata_united_default' }, { ignore: [400] }], + ]); + + expect(esClient.transform.putTransform.mock.calls).toEqual([[expectedData.TRANSFORM]]); + expect(esClient.transform.startTransform.mock.calls).toEqual([ + [ + { + transform_id: 'logs-endpoint.metadata_current-default-0.16.0-dev.0', + }, + { ignore: [409] }, + ], + ]); + + // Saved object is updated with newly created index templates, component templates, transform + expect(savedObjectsClient.update.mock.calls).toEqual([ + [ + 'epm-packages', + 'endpoint', + { + installed_es: [ + { + id: 'metrics-endpoint.policy-0.16.0-dev.0', + type: ElasticsearchAssetType.ingestPipeline, + }, + { + id: 'logs-endpoint.metadata_current-template', + type: ElasticsearchAssetType.indexTemplate, + }, + { + id: 'logs-endpoint.metadata_current-template@custom', + type: ElasticsearchAssetType.componentTemplate, + }, + { + id: 'logs-endpoint.metadata_current-template@package', + type: ElasticsearchAssetType.componentTemplate, + }, + { + id: 'logs-endpoint.metadata_current-default-0.16.0-dev.0', + type: ElasticsearchAssetType.transform, + }, + ], + }, + { + refresh: false, + }, + ], + ]); + }); + + test('can install new version when no older version', async () => { + const sourceData = getYamlTestData(true); + const expectedData = getExpectedData(); + + const previousInstallation: Installation = { + installed_es: [], + } as unknown as Installation; + + const currentInstallation: Installation = { + installed_es: [ + { + id: 'metrics-endpoint.metadata-current-default-0.16.0-dev.0', + type: ElasticsearchAssetType.transform, + }, + ], + } as unknown as Installation; + (getAsset as jest.MockedFunction).mockReturnValueOnce( + Buffer.from(sourceData.TRANSFORM, 'utf8') + ); + (getInstallation as jest.MockedFunction) + .mockReturnValueOnce(Promise.resolve(previousInstallation)) + .mockReturnValueOnce(Promise.resolve(currentInstallation)); + + ( + getInstallationObject as jest.MockedFunction + ).mockReturnValueOnce( + Promise.resolve({ + attributes: { installed_es: [] }, + } as unknown as SavedObject) + ); + + await installTransforms( + { + name: 'endpoint', + version: '0.16.0-dev.0', + } as unknown as RegistryPackage, + ['endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/transform.yml'], + esClient, + savedObjectsClient, + loggerMock.create(), + previousInstallation.installed_es + ); + + expect(esClient.transform.putTransform.mock.calls).toEqual([[expectedData.TRANSFORM]]); + expect(esClient.transform.startTransform.mock.calls).toEqual([ + [ + { + transform_id: 'logs-endpoint.metadata_current-default-0.16.0-dev.0', + }, + { ignore: [409] }, + ], + ]); + + expect(savedObjectsClient.update.mock.calls).toEqual([ + [ + 'epm-packages', + 'endpoint', + { + installed_es: [ + { id: 'logs-endpoint.metadata_current-default-0.16.0-dev.0', type: 'transform' }, + ], + }, + { + refresh: false, + }, + ], + ]); + }); + + test('can combine settings fields.yml & manifest.yml and not start transform automatically', async () => { + const sourceData = getYamlTestData(false); + const expectedData = getExpectedData(); + + const previousInstallation: Installation = { + installed_es: [ + { + id: 'endpoint.metadata-current-default-0.15.0-dev.0', + type: ElasticsearchAssetType.transform, + }, + { + id: 'logs-endpoint.metadata_current-template', + type: ElasticsearchAssetType.indexTemplate, + }, + { + id: 'logs-endpoint.metadata_current-template@custom', + type: ElasticsearchAssetType.componentTemplate, + }, + { + id: 'logs-endpoint.metadata_current-template@package', + type: ElasticsearchAssetType.componentTemplate, + }, + ], + } as unknown as Installation; + + const currentInstallation: Installation = { + installed_es: [], + } as unknown as Installation; + + (getAsset as jest.MockedFunction) + .mockReturnValueOnce(Buffer.from(sourceData.FIELDS, 'utf8')) + .mockReturnValueOnce(Buffer.from(sourceData.MANIFEST, 'utf8')) + .mockReturnValueOnce(Buffer.from(sourceData.TRANSFORM, 'utf8')); + + (getInstallation as jest.MockedFunction) + .mockReturnValueOnce(Promise.resolve(previousInstallation)) + .mockReturnValueOnce(Promise.resolve(currentInstallation)); + + ( + getInstallationObject as jest.MockedFunction + ).mockReturnValueOnce( + Promise.resolve({ + attributes: { installed_es: currentInstallation.installed_es }, + } as unknown as SavedObject) + ); + + esClient.transform.getTransform.mockResponseOnce({ + count: 1, + transforms: [ + // @ts-expect-error incomplete data + { + dest: { + index: 'mock-old-destination-index', + }, + }, + ], + }); + + await installTransforms( + { + name: 'endpoint', + version: '0.16.0-dev.0', + } as unknown as RegistryPackage, + [ + 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/fields/fields.yml', + 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/manifest.yml', + 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/transform.yml', + ], + esClient, + savedObjectsClient, + loggerMock.create(), + previousInstallation.installed_es + ); + + expect(esClient.transform.getTransform.mock.calls).toEqual([ + [ + { + transform_id: 'endpoint.metadata-current-default-0.15.0-dev.0', + }, + { ignore: [404] }, + ], + ]); + + // Transform from old version is stopped & deleted + expect(esClient.transform.stopTransform.mock.calls).toEqual([ + [ + { + transform_id: 'endpoint.metadata-current-default-0.15.0-dev.0', + force: true, + }, + { ignore: [404] }, + ], + ]); + + expect(esClient.transform.deleteTransform.mock.calls).toEqual([ + [ + { + transform_id: 'endpoint.metadata-current-default-0.15.0-dev.0', + force: true, + }, + { ignore: [404] }, + ], + ]); + + // Destination index from old version is also deleted + expect(esClient.transport.request.mock.calls).toEqual([ + [{ method: 'DELETE', path: '/mock-old-destination-index' }, { ignore: [404] }], + ]); + + // Component templates are created with mappings from fields.yml + // and template from manifest + expect(esClient.cluster.putComponentTemplate.mock.calls).toEqual([ + [ + { + name: 'logs-endpoint.metadata_current-template@package', + body: { + template: { + settings: { + index: { + codec: 'best_compression', + refresh_interval: '5s', + number_of_shards: 1, + number_of_routing_shards: 30, + hidden: true, + mapping: { total_fields: { limit: '10000' } }, + }, + }, + mappings: { + properties: { '@timestamp': { type: 'date' } }, + dynamic_templates: [ + { + strings_as_keyword: { + match_mapping_type: 'string', + mapping: { ignore_above: 1024, type: 'keyword' }, + }, + }, + ], + dynamic: false, + _meta: {}, + date_detection: false, + }, + }, + _meta: meta, + }, + create: false, + }, + { ignore: [404] }, + ], + [ + { + name: 'logs-endpoint.metadata_current-template@custom', + body: { + template: { settings: {} }, + _meta: meta, + }, + create: true, + }, + { ignore: [404] }, + ], + ]); + // Index template composed of the two component templates created + // with index pattern matching the destination index + expect(esClient.indices.putIndexTemplate.mock.calls).toEqual([ + [ + { + body: { + _meta: meta, + composed_of: [ + 'logs-endpoint.metadata_current-template@package', + 'logs-endpoint.metadata_current-template@custom', + ], + index_patterns: ['.metrics-endpoint.metadata_united_default'], + priority: 250, + template: { mappings: undefined, settings: undefined }, + }, + name: 'logs-endpoint.metadata_current-template', + }, + { ignore: [404] }, + ], + ]); + + // Destination index is created before transform is created + expect(esClient.indices.create.mock.calls).toEqual([ + [{ index: '.metrics-endpoint.metadata_united_default' }, { ignore: [400] }], + ]); + + // New transform created but not not started automatically if start: false in manifest.yml + expect(esClient.transform.putTransform.mock.calls).toEqual([[expectedData.TRANSFORM]]); + expect(esClient.transform.startTransform.mock.calls).toEqual([]); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/fleet_server_host.test.ts b/x-pack/plugins/fleet/server/services/fleet_server_host.test.ts new file mode 100644 index 000000000000..72602df6af3d --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server_host.test.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; + +import { + GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + DEFAULT_FLEET_SERVER_HOST_ID, +} from '../constants'; + +import { appContextService } from './app_context'; +import { migrateSettingsToFleetServerHost } from './fleet_server_host'; +import { getCloudFleetServersHosts } from './settings'; + +jest.mock('./app_context'); + +const mockedAppContextService = appContextService as jest.Mocked; + +describe('getCloudFleetServersHosts', () => { + afterEach(() => { + mockedAppContextService.getCloud.mockReset(); + }); + it('should return undefined if cloud is not setup', () => { + expect(getCloudFleetServersHosts()).toBeUndefined(); + }); + + it('should return fleet server hosts if cloud is correctly setup with default port == 443', () => { + mockedAppContextService.getCloud.mockReturnValue({ + cloudId: + 'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==', + isCloudEnabled: true, + deploymentId: 'deployment-id-1', + apm: {}, + }); + + expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` + Array [ + "https://deployment-id-1.fleet.us-east-1.aws.found.io", + ] + `); + }); + + it('should return fleet server hosts if cloud is correctly setup with a default port', () => { + mockedAppContextService.getCloud.mockReturnValue({ + cloudId: + 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', + isCloudEnabled: true, + deploymentId: 'deployment-id-1', + apm: {}, + }); + + expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` + Array [ + "https://deployment-id-1.fleet.test.fr:9243", + ] + `); + }); +}); + +describe('migrateSettingsToFleetServerHost', () => { + it('should not migrate settings if a default fleet server policy config exists', async () => { + const soClient = savedObjectsClientMock.create(); + soClient.find.mockImplementation(({ type }) => { + if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) { + return { saved_objects: [{ id: 'test123' }] } as any; + } + + throw new Error('Not mocked'); + }); + + await migrateSettingsToFleetServerHost(soClient); + + expect(soClient.create).not.toBeCalled(); + }); + + it('should not migrate settings if there is not old settings', async () => { + const soClient = savedObjectsClientMock.create(); + soClient.find.mockImplementation(({ type }) => { + if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) { + return { saved_objects: [] } as any; + } + + if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) { + return { + saved_objects: [], + } as any; + } + + throw new Error('Not mocked'); + }); + + soClient.create.mockResolvedValue({ + id: DEFAULT_FLEET_SERVER_HOST_ID, + attributes: {}, + } as any); + + await migrateSettingsToFleetServerHost(soClient); + expect(soClient.create).not.toBeCalled(); + }); + + it('should migrate settings to new saved object', async () => { + const soClient = savedObjectsClientMock.create(); + soClient.find.mockImplementation(({ type }) => { + if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) { + return { saved_objects: [] } as any; + } + + if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) { + return { + saved_objects: [ + { + attributes: { + fleet_server_hosts: ['https://fleetserver:8220'], + }, + }, + ], + } as any; + } + + throw new Error('Not mocked'); + }); + + soClient.create.mockResolvedValue({ + id: DEFAULT_FLEET_SERVER_HOST_ID, + attributes: {}, + } as any); + + await migrateSettingsToFleetServerHost(soClient); + expect(soClient.create).toBeCalledWith( + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + expect.objectContaining({ + is_default: true, + host_urls: ['https://fleetserver:8220'], + }), + expect.objectContaining({ + id: DEFAULT_FLEET_SERVER_HOST_ID, + }) + ); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/fleet_server_host.ts b/x-pack/plugins/fleet/server/services/fleet_server_host.ts new file mode 100644 index 000000000000..a3ade854770c --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server_host.ts @@ -0,0 +1,237 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsClientContract } from '@kbn/core/server'; + +import { + GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + DEFAULT_FLEET_SERVER_HOST_ID, + SO_SEARCH_LIMIT, +} from '../constants'; + +import type { + SettingsSOAttributes, + FleetServerHostSOAttributes, + FleetServerHost, + NewFleetServerHost, +} from '../types'; +import { FleetServerHostUnauthorizedError } from '../errors'; + +export async function createFleetServerHost( + soClient: SavedObjectsClientContract, + data: NewFleetServerHost, + options?: { id?: string; overwrite?: boolean; fromPreconfiguration?: boolean } +): Promise { + if (data.is_default) { + const defaultItem = await getDefaultFleetServerHost(soClient); + if (defaultItem) { + await updateFleetServerHost( + soClient, + defaultItem.id, + { is_default: false }, + { fromPreconfiguration: options?.fromPreconfiguration } + ); + } + } + + const res = await soClient.create( + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + data, + { id: options?.id, overwrite: options?.overwrite } + ); + + return { + id: res.id, + ...res.attributes, + }; +} + +export async function getFleetServerHost( + soClient: SavedObjectsClientContract, + id: string +): Promise { + const res = await soClient.get( + FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + id + ); + + return { + id: res.id, + ...res.attributes, + }; +} + +export async function listFleetServerHosts(soClient: SavedObjectsClientContract) { + const res = await soClient.find({ + type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + perPage: SO_SEARCH_LIMIT, + }); + + return { + items: res.saved_objects.map((so) => ({ + id: so.id, + ...so.attributes, + })), + total: res.total, + page: res.page, + perPage: res.per_page, + }; +} + +export async function deleteFleetServerHost( + soClient: SavedObjectsClientContract, + id: string, + options?: { fromPreconfiguration?: boolean } +) { + const fleetServerHost = await getFleetServerHost(soClient, id); + + if (fleetServerHost.is_preconfigured && !options?.fromPreconfiguration) { + throw new FleetServerHostUnauthorizedError( + `Cannot delete ${id} preconfigured fleet server host` + ); + } + + if (fleetServerHost.is_default) { + throw new FleetServerHostUnauthorizedError( + `Default Fleet Server hosts ${id} cannot be deleted.` + ); + } + + return await soClient.delete(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id); +} + +export async function updateFleetServerHost( + soClient: SavedObjectsClientContract, + id: string, + data: Partial, + options?: { fromPreconfiguration?: boolean } +) { + const originalItem = await getFleetServerHost(soClient, id); + + if (data.is_preconfigured && !options?.fromPreconfiguration) { + throw new FleetServerHostUnauthorizedError( + `Cannot update ${id} preconfigured fleet server host` + ); + } + + if (data.is_default) { + const defaultItem = await getDefaultFleetServerHost(soClient); + if (defaultItem && defaultItem.id !== id) { + await updateFleetServerHost( + soClient, + defaultItem.id, + { + is_default: false, + }, + { fromPreconfiguration: options?.fromPreconfiguration } + ); + } + } + + await soClient.update(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id, data); + + return { + ...originalItem, + ...data, + }; +} + +export async function bulkGetFleetServerHosts( + soClient: SavedObjectsClientContract, + ids: string[], + { ignoreNotFound = false } = { ignoreNotFound: true } +) { + if (ids.length === 0) { + return []; + } + + const res = await soClient.bulkGet( + ids.map((id) => ({ + id, + type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + })) + ); + + return res.saved_objects + .map((so) => { + if (so.error) { + if (!ignoreNotFound || so.error.statusCode !== 404) { + throw so.error; + } + return undefined; + } + + return { + id: so.id, + ...so.attributes, + }; + }) + .filter( + (fleetServerHostOrUndefined): fleetServerHostOrUndefined is FleetServerHost => + typeof fleetServerHostOrUndefined !== 'undefined' + ); +} + +/** + * Get the default Fleet server policy hosts or throw if it does not exists + */ +export async function getDefaultFleetServerHost( + soClient: SavedObjectsClientContract +): Promise { + const res = await soClient.find({ + type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + filter: `${FLEET_SERVER_HOST_SAVED_OBJECT_TYPE}.attributes.is_default:true`, + }); + + if (res.saved_objects.length === 0) { + return null; + } + + return { + id: res.saved_objects[0].id, + ...res.saved_objects[0].attributes, + }; +} + +/** + * Migrate Global setting fleet server hosts to their own saved object + */ +export async function migrateSettingsToFleetServerHost(soClient: SavedObjectsClientContract) { + const defaultFleetServerHost = await getDefaultFleetServerHost(soClient); + if (defaultFleetServerHost) { + return; + } + + const res = await soClient.find({ + type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + }); + + const oldSettings = res.saved_objects[0]; + if ( + !oldSettings || + !oldSettings.attributes.fleet_server_hosts || + oldSettings.attributes.fleet_server_hosts.length === 0 + ) { + return; + } + + // Migrate + await createFleetServerHost( + soClient, + { + name: 'Default', + host_urls: oldSettings.attributes.fleet_server_hosts, + is_default: true, + is_preconfigured: false, + }, + { + id: DEFAULT_FLEET_SERVER_HOST_ID, + overwrite: true, + } + ); +} diff --git a/x-pack/plugins/fleet/server/services/index.ts b/x-pack/plugins/fleet/server/services/index.ts index a23a049bdc76..aafbb383cbb0 100644 --- a/x-pack/plugins/fleet/server/services/index.ts +++ b/x-pack/plugins/fleet/server/services/index.ts @@ -59,3 +59,6 @@ export { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration'; // Package Services export { PackageServiceImpl } from './epm'; export type { PackageService, PackageClient } from './epm'; + +// Fleet server policy config +export { migrateSettingsToFleetServerHost } from './fleet_server_host'; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.test.ts new file mode 100644 index 000000000000..468058f87f44 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getPreconfiguredFleetServerHostFromConfig } from './fleet_server_host'; + +jest.mock('../fleet_server_host'); + +describe('getPreconfiguredFleetServerHostFromConfig', () => { + it('should work with preconfigured fleetServerHosts', () => { + const config = { + fleetServerHosts: [ + { + id: 'fleet-123', + name: 'TEST', + is_default: true, + host_urls: ['http://test.fr'], + }, + ], + }; + + const res = getPreconfiguredFleetServerHostFromConfig(config); + + expect(res).toEqual(config.fleetServerHosts); + }); + + it('should work with agents.fleet_server.hosts', () => { + const config = { + agents: { fleet_server: { hosts: ['http://test.fr'] } }, + }; + + const res = getPreconfiguredFleetServerHostFromConfig(config); + + expect(res).toEqual([ + { + id: 'fleet-default-fleet-server-host', + name: 'Default', + host_urls: ['http://test.fr'], + is_default: true, + }, + ]); + }); + + it('should work with agents.fleet_server.hosts and preconfigured outputs', () => { + const config = { + agents: { fleet_server: { hosts: ['http://test.fr'] } }, + fleetServerHosts: [ + { + id: 'fleet-123', + name: 'TEST', + is_default: false, + host_urls: ['http://test.fr'], + }, + ], + }; + + const res = getPreconfiguredFleetServerHostFromConfig(config); + + expect(res).toHaveLength(2); + expect(res.map(({ id }) => id)).toEqual(['fleet-123', 'fleet-default-fleet-server-host']); + }); + + it('should throw if there is multiple default outputs', () => { + const config = { + agents: { fleet_server: { hosts: ['http://test.fr'] } }, + fleetServerHosts: [ + { + id: 'fleet-123', + name: 'TEST', + is_default: true, + host_urls: ['http://test.fr'], + }, + ], + }; + + expect(() => getPreconfiguredFleetServerHostFromConfig(config)).toThrowError( + /Only one default Fleet Server host is allowed/ + ); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts new file mode 100644 index 000000000000..465a2f8706ea --- /dev/null +++ b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsClientContract } from '@kbn/core/server'; +import { isEqual } from 'lodash'; + +import type { FleetConfigType } from '../../config'; +import { DEFAULT_FLEET_SERVER_HOST_ID } from '../../constants'; + +import type { FleetServerHost } from '../../types'; +import { + bulkGetFleetServerHosts, + createFleetServerHost, + deleteFleetServerHost, + listFleetServerHosts, + updateFleetServerHost, +} from '../fleet_server_host'; + +export function getPreconfiguredFleetServerHostFromConfig(config?: FleetConfigType) { + const { fleetServerHosts: fleetServerHostsFromConfig } = config; + + const legacyFleetServerHostsConfig = getConfigFleetServerHosts(config); + + const fleetServerHosts: FleetServerHost[] = (fleetServerHostsFromConfig || []).concat([ + ...(legacyFleetServerHostsConfig + ? [ + { + name: 'Default', + is_default: true, + id: DEFAULT_FLEET_SERVER_HOST_ID, + host_urls: legacyFleetServerHostsConfig, + }, + ] + : []), + ]); + + if (fleetServerHosts.filter((fleetServerHost) => fleetServerHost.is_default).length > 1) { + throw new Error('Only one default Fleet Server host is allowed'); + } + + return fleetServerHosts; +} + +export async function ensurePreconfiguredFleetServerHosts( + soClient: SavedObjectsClientContract, + preconfiguredFleetServerHosts: FleetServerHost[] +) { + await createOrUpdatePreconfiguredFleetServerHosts(soClient, preconfiguredFleetServerHosts); + await cleanPreconfiguredFleetServerHosts(soClient, preconfiguredFleetServerHosts); +} + +export async function createOrUpdatePreconfiguredFleetServerHosts( + soClient: SavedObjectsClientContract, + preconfiguredFleetServerHosts: FleetServerHost[] +) { + const existingFleetServerHosts = await bulkGetFleetServerHosts( + soClient, + preconfiguredFleetServerHosts.map(({ id }) => id), + { ignoreNotFound: true } + ); + + await Promise.all( + preconfiguredFleetServerHosts.map(async (preconfiguredFleetServerHost) => { + const existingHost = existingFleetServerHosts.find( + (fleetServerHost) => fleetServerHost.id === preconfiguredFleetServerHost.id + ); + + const { id, ...data } = preconfiguredFleetServerHost; + + const isCreate = !existingHost; + const isUpdateWithNewData = + existingHost && + (!existingHost.is_preconfigured || + existingHost.is_default !== preconfiguredFleetServerHost.is_default || + existingHost.name !== preconfiguredFleetServerHost.name || + !isEqual(existingHost?.host_urls, preconfiguredFleetServerHost.host_urls)); + + if (isCreate) { + await createFleetServerHost( + soClient, + { + ...data, + is_preconfigured: true, + }, + { id, overwrite: true, fromPreconfiguration: true } + ); + } else if (isUpdateWithNewData) { + await updateFleetServerHost( + soClient, + id, + { + ...data, + is_preconfigured: true, + }, + { fromPreconfiguration: true } + ); + // TODO Bump revision of all policies using that output + } + }) + ); +} + +export async function cleanPreconfiguredFleetServerHosts( + soClient: SavedObjectsClientContract, + preconfiguredFleetServerHosts: FleetServerHost[] +) { + const existingFleetServerHosts = await listFleetServerHosts(soClient); + const existingPreconfiguredHosts = existingFleetServerHosts.items.filter( + (o) => o.is_preconfigured === true + ); + + for (const existingFleetServerHost of existingPreconfiguredHosts) { + const hasBeenDelete = !preconfiguredFleetServerHosts.find( + ({ id }) => existingFleetServerHost.id === id + ); + if (!hasBeenDelete) { + continue; + } + + if (existingFleetServerHost.is_default) { + await updateFleetServerHost( + soClient, + existingFleetServerHost.id, + { is_preconfigured: false }, + { + fromPreconfiguration: true, + } + ); + } else { + await deleteFleetServerHost(soClient, existingFleetServerHost.id, { + fromPreconfiguration: true, + }); + } + } +} + +function getConfigFleetServerHosts(config?: FleetConfigType) { + return config?.agents?.fleet_server?.hosts && config.agents.fleet_server.hosts.length > 0 + ? config?.agents?.fleet_server?.hosts + : undefined; +} diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index eb4a9313e169..37f368a4b864 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -44,6 +44,11 @@ import { upgradeManagedPackagePolicies } from './managed_package_policies'; import { getBundledPackages } from './epm/packages'; import { upgradePackageInstallVersion } from './setup/upgrade_package_install_version'; import { upgradeAgentPolicySchemaVersion } from './setup/upgrade_agent_policy_schema_version'; +import { migrateSettingsToFleetServerHost } from './fleet_server_host'; +import { + ensurePreconfiguredFleetServerHosts, + getPreconfiguredFleetServerHostFromConfig, +} from './preconfiguration/fleet_server_host'; export interface SetupStatus { isInitialized: boolean; @@ -70,25 +75,32 @@ async function createSetupSideEffects( const { agentPolicies: policiesOrUndefined, packages: packagesOrUndefined } = appContextService.getConfig() ?? {}; - const policies = policiesOrUndefined ?? []; let packages = packagesOrUndefined ?? []; + logger.debug('Setting Fleet server config'); + await migrateSettingsToFleetServerHost(soClient); + logger.debug('Setting up Fleet download source'); + const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient); + logger.debug('Setting up Fleet outputs'); + await ensurePreconfiguredFleetServerHosts( + soClient, + getPreconfiguredFleetServerHostFromConfig(appContextService.getConfig()) + ); await Promise.all([ ensurePreconfiguredOutputs( soClient, esClient, getPreconfiguredOutputFromConfig(appContextService.getConfig()) ), + settingsService.settingsSetup(soClient), ]); const defaultOutput = await outputService.ensureDefaultOutput(soClient); - const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient); - if (appContextService.getConfig()?.agentIdVerificationEnabled) { logger.debug('Setting up Fleet Elasticsearch assets'); await ensureFleetGlobalEsAssets(soClient, esClient); diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index fd0cee334cc5..ea662f51864f 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -36,6 +36,9 @@ export type { OutputType, EnrollmentAPIKey, EnrollmentAPIKeySOAttributes, + NewFleetServerHost, + FleetServerHost, + FleetServerHostSOAttributes, Installation, EpmPackageInstallStatus, InstallationStatus, diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index ad4f1958a1a2..3100fb04a46f 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -85,6 +85,16 @@ export const PreconfiguredOutputsSchema = schema.arrayOf( } ); +export const PreconfiguredFleetServerHostsSchema = schema.arrayOf( + schema.object({ + id: schema.string(), + name: schema.string(), + is_default: schema.boolean({ defaultValue: false }), + host_urls: schema.arrayOf(schema.string(), { minSize: 1 }), + }), + { defaultValue: [] } +); + export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( schema.object({ ...AgentPolicyBaseSchema, diff --git a/x-pack/plugins/fleet/server/types/rest_spec/fleet_server_policy_config.ts b/x-pack/plugins/fleet/server/types/rest_spec/fleet_server_policy_config.ts new file mode 100644 index 000000000000..b011d9065fd4 --- /dev/null +++ b/x-pack/plugins/fleet/server/types/rest_spec/fleet_server_policy_config.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const PostFleetServerHostRequestSchema = { + body: schema.object({ + id: schema.maybe(schema.string()), + name: schema.string(), + host_urls: schema.arrayOf(schema.string(), { minSize: 1 }), + is_default: schema.boolean({ defaultValue: false }), + }), +}; + +export const GetOneFleetServerHostRequestSchema = { + params: schema.object({ itemId: schema.string() }), +}; + +export const PutFleetServerHostRequestSchema = { + params: schema.object({ itemId: schema.string() }), + body: schema.object({ + name: schema.maybe(schema.string()), + host_urls: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })), + is_default: schema.maybe(schema.boolean({ defaultValue: false })), + }), +}; + +export const GetAllFleetServerHostRequestSchema = {}; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/index.ts b/x-pack/plugins/fleet/server/types/rest_spec/index.ts index 98f14cb0c879..ffe8514073c3 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/index.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/index.ts @@ -11,6 +11,7 @@ export * from './agent'; export * from './package_policy'; export * from './epm'; export * from './enrollment_api_key'; +export * from './fleet_server_policy_config'; export * from './output'; export * from './preconfiguration'; export * from './settings'; diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 320843546a30..c9c730b6a170 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -27,6 +27,7 @@ { "path": "../licensing/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, { "path": "../encrypted_saved_objects/tsconfig.json" }, + {"path": "../../../src/plugins/guided_onboarding/tsconfig.json"}, // optionalPlugins from ./kibana.json { "path": "../security/tsconfig.json" }, diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts index 95cba8b75c4b..1e3d51a6941c 100644 --- a/x-pack/plugins/lens/common/constants.ts +++ b/x-pack/plugins/lens/common/constants.ts @@ -44,12 +44,6 @@ export const LegendDisplay = { HIDE: 'hide', } as const; -export const layerTypes = { - DATA: 'data', - REFERENCELINE: 'referenceLine', - ANNOTATIONS: 'annotations', -} as const; - // might collide with user-supplied field names, try to make as unique as possible export const DOCUMENT_FIELD_NAME = '___records___'; diff --git a/x-pack/plugins/lens/common/embeddable_factory/index.ts b/x-pack/plugins/lens/common/embeddable_factory/index.ts index 8ddddf654b01..b794ec642f40 100644 --- a/x-pack/plugins/lens/common/embeddable_factory/index.ts +++ b/x-pack/plugins/lens/common/embeddable_factory/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { SerializableRecord, Serializable } from '@kbn/utility-types'; -import { SavedObjectReference } from '@kbn/core/types'; +import type { SerializableRecord, Serializable } from '@kbn/utility-types'; +import type { SavedObjectReference } from '@kbn/core/types'; import type { EmbeddableStateWithType, EmbeddableRegistryDefinition, diff --git a/x-pack/plugins/lens/common/expressions/datatable/datatable_column.ts b/x-pack/plugins/lens/common/expressions/datatable/datatable_column.ts index ae155fe560e8..a7592f3f179b 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/datatable_column.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/datatable_column.ts @@ -9,7 +9,7 @@ import type { Direction } from '@elastic/eui'; import type { PaletteOutput, CustomPaletteParams } from '@kbn/coloring'; import type { CustomPaletteState } from '@kbn/charts-plugin/common'; import type { ExpressionFunctionDefinition, DatatableColumn } from '@kbn/expressions-plugin/common'; -import { SortingHint } from '../..'; +import type { SortingHint } from '../..'; export type LensGridDirection = 'none' | Direction; diff --git a/x-pack/plugins/lens/common/expressions/datatable/index.ts b/x-pack/plugins/lens/common/expressions/datatable/index.ts index cf9fb1d0b479..2fa031236029 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/index.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/index.ts @@ -7,8 +7,5 @@ export * from './datatable_column'; export * from './datatable'; -export * from './summary'; -export * from './transpose_helpers'; -export * from './utils'; export type { DatatableProps } from './types'; diff --git a/x-pack/plugins/lens/common/expressions/datatable/sorting.tsx b/x-pack/plugins/lens/common/expressions/datatable/sorting.tsx index bfae4544c597..e3db99d69dc7 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/sorting.tsx +++ b/x-pack/plugins/lens/common/expressions/datatable/sorting.tsx @@ -7,8 +7,7 @@ import versionCompare from 'compare-versions'; import valid from 'semver/functions/valid'; -import ipaddr from 'ipaddr.js'; -import type { IPv4, IPv6 } from 'ipaddr.js'; +import ipaddr, { type IPv4, type IPv6 } from 'ipaddr.js'; import type { FieldFormat } from '@kbn/field-formats-plugin/common'; function isIPv6Address(ip: IPv4 | IPv6): ip is IPv6 { diff --git a/x-pack/plugins/lens/common/expressions/datatable/summary.ts b/x-pack/plugins/lens/common/expressions/datatable/summary.ts index fe45a91b9a86..4516abfabb06 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/summary.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/summary.ts @@ -85,6 +85,7 @@ export function getSummaryRowOptions(): Array<{ ]; } +/** @internal **/ export function computeSummaryRowForColumn( columnArgs: ColumnConfigArg, table: Datatable, diff --git a/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.ts b/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.ts index f99977f74701..00e0de71395d 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.ts @@ -14,7 +14,7 @@ const TRANSPOSE_SEPARATOR = '---'; const TRANSPOSE_VISUAL_SEPARATOR = '›'; -export function getTransposeId(value: string, columnId: string) { +function getTransposeId(value: string, columnId: string) { return `${value}${TRANSPOSE_SEPARATOR}${columnId}`; } @@ -36,6 +36,7 @@ export function getOriginalId(id: string) { * * If the table is tranposed by multiple columns, this process is repeated on top of the previous transformation. * + * @internal * @param args Arguments for the table visualization * @param firstTable datatable object containing the actual data * @param formatters Formatters for all columns to transpose columns by actual display values diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts index b9b4fb488999..a2b7d730766c 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts @@ -12,18 +12,6 @@ import type { TimeRange } from '@kbn/es-query'; import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks'; import { functionWrapper } from '@kbn/expressions-plugin/common/expression_functions/specs/tests/utils'; -// mock the specific inner variable: -// there are intra dependencies in the data plugin we might break trying to mock the whole thing -jest.mock('@kbn/data-plugin/common/query/timefilter/get_time', () => { - const localMoment = jest.requireActual('moment'); - return { - calculateBounds: jest.fn(({ from, to }) => ({ - min: localMoment(from), - max: localMoment(to), - })), - }; -}); - import { getTimeScale } from './time_scale'; import type { TimeScaleArgs } from './types'; @@ -34,7 +22,11 @@ describe('time_scale', () => { context?: ExecutionContext ) => Promise; - const timeScale = getTimeScale(createDatatableUtilitiesMock, () => 'UTC'); + const timeScale = getTimeScale( + createDatatableUtilitiesMock, + () => 'UTC', + () => new Date('2010-01-04T06:30:30') + ); const emptyTable: Datatable = { type: 'datatable', @@ -402,7 +394,6 @@ describe('time_scale', () => { ...emptyTable, rows: [ { - date: moment('2010-01-01T00:00:00.000Z').valueOf(), metric: 300, }, ], @@ -425,6 +416,34 @@ describe('time_scale', () => { expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([75]); }); + it('should work with relative time range', async () => { + const result = await timeScaleWrapped( + { + ...emptyTable, + rows: [ + { + metric: 300, + }, + ], + }, + { + inputColumnId: 'metric', + outputColumnId: 'scaledMetric', + targetUnit: 'd', + }, + { + getSearchContext: () => ({ + timeRange: { + from: 'now-2d', + to: 'now', + }, + }), + } as unknown as ExecutionContext + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([150]); + }); + it('should apply fn for non-histogram fields (with Reduced time range)', async () => { const result = await timeScaleWrapped( { diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts index 722056321e17..6b2e1857a902 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts @@ -24,12 +24,39 @@ const unitInMs: Record = { d: 1000 * 60 * 60 * 24, }; +// the datemath plugin always parses dates by using the current default moment time zone. +// to use the configured time zone, we are temporary switching it just for the calculation. + +// The code between this call and the reset in the finally block is not allowed to get async, +// otherwise the timezone setting can leak out of this function. +const withChangedTimeZone = ( + timeZone: string | undefined, + action: () => TReturnedValue +): TReturnedValue => { + if (timeZone) { + const defaultTimezone = moment().zoneName(); + try { + moment.tz.setDefault(timeZone); + return action(); + } finally { + // reset default moment timezone + moment.tz.setDefault(defaultTimezone); + } + } else { + return action(); + } +}; + +const getTimeBounds = (timeRange: TimeRange, timeZone?: string, getForceNow?: () => Date) => + withChangedTimeZone(timeZone, () => calculateBounds(timeRange, { forceNow: getForceNow?.() })); + export const timeScaleFn = ( getDatatableUtilities: ( context: ExecutionContext ) => DatatableUtilitiesService | Promise, - getTimezone: (context: ExecutionContext) => string | Promise + getTimezone: (context: ExecutionContext) => string | Promise, + getForceNow?: () => Date ): TimeScaleExpressionFunction['fn'] => async ( input, @@ -69,7 +96,9 @@ export const timeScaleFn = timeZone: contextTimeZone, }); const intervalDuration = timeInfo?.interval && parseInterval(timeInfo.interval); - timeBounds = timeInfo?.timeRange && calculateBounds(timeInfo.timeRange); + + timeBounds = + timeInfo?.timeRange && getTimeBounds(timeInfo.timeRange, timeInfo?.timeZone, getForceNow); getStartEndOfBucketMeta = (row) => { const startOfBucket = moment.tz(row[dateColumnId], timeInfo?.timeZone ?? contextTimeZone); @@ -89,8 +118,18 @@ export const timeScaleFn = } } else { const timeRange = context.getSearchContext().timeRange as TimeRange; - const endOfBucket = moment.tz(timeRange.to, contextTimeZone); - let startOfBucket = moment.tz(timeRange.from, contextTimeZone); + timeBounds = getTimeBounds(timeRange, contextTimeZone, getForceNow); + + if (!timeBounds.max || !timeBounds.min) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.timeBoundsMissingMessage', { + defaultMessage: 'Could not parse "Time Range"', + }) + ); + } + + const endOfBucket = timeBounds.max; + let startOfBucket = timeBounds.min; if (reducedTimeRange) { const reducedStartOfBucket = endOfBucket.clone().subtract(parseInterval(reducedTimeRange)); @@ -100,8 +139,6 @@ export const timeScaleFn = } } - timeBounds = calculateBounds(timeRange); - getStartEndOfBucketMeta = () => ({ startOfBucket, endOfBucket, diff --git a/x-pack/plugins/lens/common/constants.test.ts b/x-pack/plugins/lens/common/helpers.test.ts similarity index 100% rename from x-pack/plugins/lens/common/constants.test.ts rename to x-pack/plugins/lens/common/helpers.test.ts diff --git a/x-pack/plugins/lens/common/index.ts b/x-pack/plugins/lens/common/index.ts index 7929ebf6d178..45dd41422b01 100644 --- a/x-pack/plugins/lens/common/index.ts +++ b/x-pack/plugins/lens/common/index.ts @@ -10,7 +10,6 @@ export * from './constants'; export * from './types'; -export * from './visualizations'; // Note: do not import the expression folder here or the page bundle will be bloated with all // the package diff --git a/x-pack/plugins/lens/common/types.ts b/x-pack/plugins/lens/common/types.ts index c21da9e31475..c3410f986017 100644 --- a/x-pack/plugins/lens/common/types.ts +++ b/x-pack/plugins/lens/common/types.ts @@ -6,19 +6,14 @@ */ import type { Filter, FilterMeta } from '@kbn/es-query'; -import { Position } from '@elastic/charts'; -import { $Values } from '@kbn/utility-types'; +import type { Position } from '@elastic/charts'; +import type { $Values } from '@kbn/utility-types'; import type { CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; import type { IFieldFormat, SerializedFieldFormat } from '@kbn/field-formats-plugin/common'; import type { ColorMode } from '@kbn/charts-plugin/common'; -import { LegendSize } from '@kbn/visualizations-plugin/common'; -import { - CategoryDisplay, - layerTypes, - LegendDisplay, - NumberDisplay, - PieChartTypes, -} from './constants'; +import { LayerTypes } from '@kbn/expression-xy-plugin/common'; +import type { LegendSize } from '@kbn/visualizations-plugin/common'; +import { CategoryDisplay, LegendDisplay, NumberDisplay, PieChartTypes } from './constants'; export type { OriginalColumn } from './expressions/map_to_columns'; @@ -44,11 +39,7 @@ export interface PersistableFilter extends Filter { export type SortingHint = 'version'; -export type CustomPaletteParamsConfig = CustomPaletteParams & { - maxSteps?: number; -}; - -export type LayerType = typeof layerTypes[keyof typeof layerTypes]; +export type LayerType = typeof LayerTypes[keyof typeof LayerTypes]; export type ValueLabelConfig = 'hide' | 'show'; diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index b84353e116f7..5f0b48608dab 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -25,12 +25,12 @@ "expressionGauge", "expressionMetricVis", "expressionHeatmap", + "expressionXY", "eventAnnotation", "unifiedSearch", "unifiedFieldList" ], "optionalPlugins": [ - "expressionXY", "usageCollection", "taskManager", "globalSearch", diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 58e65b3b79bb..648fd6120394 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -405,39 +405,6 @@ describe('Lens App', () => { }); describe('TopNavMenu#dataViewPickerProps', () => { - it('calls the nav component with the correct dataview picker props if no permissions are given', async () => { - const { instance, lensStore } = await mountWith({ preloadedState: {} }); - const document = { - savedObjectId: defaultSavedObjectId, - state: { - query: 'fake query', - filters: [{ query: { match_phrase: { src: 'test' } } }], - }, - references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], - } as unknown as Document; - - act(() => { - lensStore.dispatch( - setState({ - query: 'fake query' as unknown as Query, - persistedDoc: document, - }) - ); - }); - instance.update(); - const props = instance - .find('[data-test-subj="lnsApp_topNav"]') - .prop('dataViewPickerComponentProps') as TopNavMenuData[]; - expect(props).toEqual( - expect.objectContaining({ - currentDataViewId: 'mockip', - onChangeDataView: expect.any(Function), - onDataViewCreated: undefined, - onAddField: undefined, - }) - ); - }); - it('calls the nav component with the correct dataview picker props if permissions are given', async () => { const { instance, lensStore, services } = await mountWith({ preloadedState: {} }); services.dataViewEditor.userPermissions.editDataView = () => true; diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index c381d519f366..4176e5f1e51a 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -259,7 +259,6 @@ export const LensTopNavMenu = ({ [dispatch] ); const [indexPatterns, setIndexPatterns] = useState([]); - const [dataViewsList, setDataViewsList] = useState([]); const [currentIndexPattern, setCurrentIndexPattern] = useState(); const [isOnTextBasedMode, setIsOnTextBasedMode] = useState(false); const [rejectedIndexPatterns, setRejectedIndexPatterns] = useState([]); @@ -289,7 +288,8 @@ export const LensTopNavMenu = ({ ] ); - const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + const canEditDataView = + Boolean(dataViewEditor?.userPermissions.editDataView()) || !currentIndexPattern?.isPersisted(); const closeFieldEditor = useRef<() => void | undefined>(); const closeDataViewEditor = useRef<() => void | undefined>(); @@ -357,27 +357,18 @@ export const LensTopNavMenu = ({ ]); useEffect(() => { - if (activeDatasourceId && datasourceStates[activeDatasourceId].state) { - const dataViewId = datasourceMap[activeDatasourceId].getUsedDataView( - datasourceStates[activeDatasourceId].state - ); - const dataView = dataViewsList.find((pattern) => pattern.id === dataViewId); - setCurrentIndexPattern(dataView ?? indexPatterns[0]); - } - }, [activeDatasourceId, datasourceMap, datasourceStates, indexPatterns, dataViewsList]); - - useEffect(() => { - const fetchDataViews = async () => { - const totalDataViewsList = []; - const dataViewsIds = await data.dataViews.getIds(); - for (let i = 0; i < dataViewsIds.length; i++) { - const d = await data.dataViews.get(dataViewsIds[i]); - totalDataViewsList.push(d); + const setCurrentPattern = async () => { + if (activeDatasourceId && datasourceStates[activeDatasourceId].state) { + const dataViewId = datasourceMap[activeDatasourceId].getUsedDataView( + datasourceStates[activeDatasourceId].state + ); + const dataView = await data.dataViews.get(dataViewId); + setCurrentIndexPattern(dataView ?? indexPatterns[0]); } - setDataViewsList(totalDataViewsList); }; - fetchDataViews(); - }, [data]); + + setCurrentPattern(); + }, [activeDatasourceId, datasourceMap, datasourceStates, indexPatterns, data.dataViews]); useEffect(() => { if (typeof query === 'object' && query !== null && isOfAggregateQueryType(query)) { @@ -583,7 +574,7 @@ export const LensTopNavMenu = ({ dataViewSpec: dataViews.indexPatterns[meta.id]?.spec, timeRange: data.query.timefilter.timefilter.getTime(), filters: newFilters, - query: newQuery, + query: isOnTextBasedMode ? query : newQuery, columns: meta.columns, }); }, @@ -626,6 +617,7 @@ export const LensTopNavMenu = ({ indexPatterns, dataViews.indexPatterns, data.query.timefilter.timefilter, + isOnTextBasedMode, lensStore, theme$, ]); @@ -765,39 +757,32 @@ export const LensTopNavMenu = ({ [editField, canEditDataView] ); - const createNewDataView = useMemo( - () => - canEditDataView - ? () => { - closeDataViewEditor.current = dataViewEditor.openEditor({ - onSave: async (dataView) => { - if (dataView.id) { - if (isOnTextBasedMode) { - dispatch( - switchAndCleanDatasource({ - newDatasourceId: 'indexpattern', - visualizationId: visualization?.activeId, - currentIndexPatternId: dataView?.id, - }) - ); - } - dispatchChangeIndexPattern(dataView); - setCurrentIndexPattern(dataView); - } - }, - allowAdHocDataView: true, - }); + const createNewDataView = useCallback(() => { + closeDataViewEditor.current = dataViewEditor.openEditor({ + onSave: async (dataView) => { + if (dataView.id) { + if (isOnTextBasedMode) { + dispatch( + switchAndCleanDatasource({ + newDatasourceId: 'indexpattern', + visualizationId: visualization?.activeId, + currentIndexPatternId: dataView?.id, + }) + ); } - : undefined, - [ - canEditDataView, - dataViewEditor, - dispatch, - dispatchChangeIndexPattern, - isOnTextBasedMode, - visualization?.activeId, - ] - ); + dispatchChangeIndexPattern(dataView); + setCurrentIndexPattern(dataView); + } + }, + allowAdHocDataView: true, + }); + }, [ + dataViewEditor, + dispatch, + dispatchChangeIndexPattern, + isOnTextBasedMode, + visualization?.activeId, + ]); const onCreateDefaultAdHocDataView = useCallback( async (pattern: string) => { @@ -846,10 +831,8 @@ export const LensTopNavMenu = ({ onDataViewCreated: createNewDataView, onCreateDefaultAdHocDataView, adHocDataViews: indexPatterns.filter((pattern) => !pattern.isPersisted()), - onChangeDataView: (newIndexPatternId: string) => { - const currentDataView = dataViewsList.find( - (indexPattern) => indexPattern.id === newIndexPatternId - ); + onChangeDataView: async (newIndexPatternId: string) => { + const currentDataView = await data.dataViews.get(newIndexPatternId); setCurrentIndexPattern(currentDataView); dispatchChangeIndexPattern(newIndexPatternId); if (isOnTextBasedMode) { @@ -863,6 +846,39 @@ export const LensTopNavMenu = ({ setIsOnTextBasedMode(false); } }, + onEditDataView: async (updatedDataViewStub) => { + if (!currentIndexPattern) return; + if (currentIndexPattern.isPersisted()) { + // clear instance cache and fetch again to make sure fields are up to date (in case pattern changed) + dataViewsService.clearInstanceCache(currentIndexPattern.id); + const updatedCurrentIndexPattern = await dataViewsService.get(currentIndexPattern.id!); + // if the data view was persisted, reload it from cache + const updatedCache = { + ...dataViews.indexPatterns, + }; + delete updatedCache[currentIndexPattern.id!]; + const newIndexPatterns = await indexPatternService.ensureIndexPattern({ + id: updatedCurrentIndexPattern.id!, + cache: updatedCache, + }); + dispatch( + changeIndexPattern({ + dataViews: { indexPatterns: newIndexPatterns }, + indexPatternId: updatedCurrentIndexPattern.id!, + }) + ); + // Renew session id to make sure the request is done again + dispatchSetState({ + searchSessionId: data.search.session.start(), + resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), + }); + // update list of index patterns to pick up mutations in the changed data view + setCurrentIndexPattern(updatedCurrentIndexPattern); + } else { + // if it was an ad-hoc data view, we need to switch to a new data view anyway + indexPatternService.replaceDataViewId(updatedDataViewStub); + } + }, textBasedLanguages: supportedTextBasedLanguages as DataViewPickerProps['textBasedLanguages'], }; diff --git a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts index 7030e58982ee..bd6b7a000ecd 100644 --- a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts +++ b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts @@ -118,10 +118,22 @@ describe('getLayerMetaInfo', () => { }); it('should not be visible if discover is not available', () => { + const mockDatasource = createMockDatasource('testDatasource'); + const updatedPublicAPI: DatasourcePublicAPI = { + datasourceId: 'indexpattern', + getOperationForColumnId: jest.fn(), + getTableSpec: jest.fn(() => [{ columnId: 'col1', fields: ['bytes'] }]), + getVisualDefaults: jest.fn(), + getSourceId: jest.fn(), + getMaxPossibleNumValues: jest.fn(), + getFilters: jest.fn(() => ({ error: 'filters error' })), + isTextBasedLanguage: jest.fn(() => false), + }; + mockDatasource.getPublicAPI.mockReturnValue(updatedPublicAPI); // both capabilities should be enabled to enable discover expect( getLayerMetaInfo( - createMockDatasource('testDatasource'), + mockDatasource, {}, { datatable1: { type: 'datatable', columns: [], rows: [] }, @@ -136,7 +148,7 @@ describe('getLayerMetaInfo', () => { ).toBeFalsy(); expect( getLayerMetaInfo( - createMockDatasource('testDatasource'), + mockDatasource, {}, { datatable1: { type: 'datatable', columns: [], rows: [] }, diff --git a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts index d5c264c5ade0..6597046c9a27 100644 --- a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts +++ b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts @@ -110,16 +110,7 @@ export function getLayerMetaInfo( isVisible, }; } - // maybe add also datasourceId validation here? - if (datasourceAPI.datasourceId !== 'indexpattern') { - return { - meta: undefined, - error: i18n.translate('xpack.lens.app.showUnderlyingDataUnsupportedDatasource', { - defaultMessage: 'Underlying data does not support the current datasource', - }), - isVisible, - }; - } + const tableSpec = datasourceAPI.getTableSpec(); const columnsWithNoTimeShifts = tableSpec.filter( diff --git a/x-pack/plugins/lens/public/data_views_service/loader.ts b/x-pack/plugins/lens/public/data_views_service/loader.ts index 9ffb99a54f40..3bb1bdf0612a 100644 --- a/x-pack/plugins/lens/public/data_views_service/loader.ts +++ b/x-pack/plugins/lens/public/data_views_service/loader.ts @@ -47,6 +47,9 @@ export function convertDataViewIntoLensIndexPattern( meta: dataView.metaFields.includes(field.name), esTypes: field.esTypes, scripted: field.scripted, + isMapped: field.isMapped, + customLabel: field.customLabel, + runtimeField: field.runtimeField, runtime: Boolean(field.runtimeField), timeSeriesMetricType: field.timeSeriesMetric, timeSeriesRollup: field.isRolledUpField, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx index c38a9a349370..dee24edcad17 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx @@ -17,8 +17,8 @@ import { EuiFlexGroup, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - -import { LayerType, layerTypes } from '../../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; +import type { LayerType } from '../../../../common'; import type { FramePublicAPI, Visualization } from '../../../types'; interface AddLayerButtonProps { @@ -29,7 +29,7 @@ interface AddLayerButtonProps { } export function getLayerType(visualization: Visualization, state: unknown, layerId: string) { - return visualization.getLayerType(layerId, state) || layerTypes.DATA; + return visualization.getLayerType(layerId, state) || LayerTypes.DATA; } export function AddLayerButton({ @@ -120,7 +120,7 @@ export function AddLayerButton({ toolTipContent, disabled, name: - type === layerTypes.ANNOTATIONS ? ( + type === LayerTypes.ANNOTATIONS ? ( {label} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index 102b1e3f2dd2..5fbfb0b86e11 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -24,7 +24,8 @@ import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import { generateId } from '../../../id_generator'; import { mountWithProvider } from '../../../mocks'; -import { LayerType, layerTypes } from '../../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; +import type { LayerType } from '../../../../common'; import { ReactWrapper } from 'enzyme'; import { addLayer } from '../../../state_management'; import { AddLayerButton } from './add_layer'; @@ -266,7 +267,7 @@ describe('ConfigPanel', () => { describe('initial default value', () => { function clickToAddLayer( instance: ReactWrapper, - layerType: LayerType = layerTypes.REFERENCELINE + layerType: LayerType = LayerTypes.REFERENCELINE ) { act(() => { instance.find('[data-test-subj="lnsLayerAddButton"]').first().simulate('click'); @@ -295,9 +296,9 @@ describe('ConfigPanel', () => { const visualizationMap = mockVisualizationMap(); visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ - { type: layerTypes.DATA, label: 'Data Layer' }, + { type: LayerTypes.DATA, label: 'Data Layer' }, { - type: layerTypes.REFERENCELINE, + type: LayerTypes.REFERENCELINE, label: 'Reference layer', }, ]); @@ -318,7 +319,7 @@ describe('ConfigPanel', () => { visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: 'Data Layer', initialDimensions: [ { @@ -329,7 +330,7 @@ describe('ConfigPanel', () => { ], }, { - type: layerTypes.REFERENCELINE, + type: LayerTypes.REFERENCELINE, label: 'Reference layer', }, ]); @@ -345,9 +346,9 @@ describe('ConfigPanel', () => { const datasourceMap = mockDatasourceMap(); const visualizationMap = mockVisualizationMap(); visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ - { type: layerTypes.DATA, label: 'Data Layer' }, + { type: LayerTypes.DATA, label: 'Data Layer' }, { - type: layerTypes.REFERENCELINE, + type: LayerTypes.REFERENCELINE, label: 'Reference layer', initialDimensions: [ { @@ -383,7 +384,7 @@ describe('ConfigPanel', () => { const visualizationMap = mockVisualizationMap(); visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: 'Data Layer', initialDimensions: [ { @@ -420,7 +421,7 @@ describe('ConfigPanel', () => { visualizationMap.testVis.setDimension = jest.fn(); visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: 'Data Layer', initialDimensions: [ { @@ -431,11 +432,11 @@ describe('ConfigPanel', () => { ], }, { - type: layerTypes.REFERENCELINE, + type: LayerTypes.REFERENCELINE, label: 'Reference layer', }, { - type: layerTypes.ANNOTATIONS, + type: LayerTypes.ANNOTATIONS, label: 'Annotations Layer', noDatasource: true, initialDimensions: [ @@ -451,7 +452,7 @@ describe('ConfigPanel', () => { datasourceMap.testDatasource.initializeDimension = jest.fn(); const props = getDefaultProps({ visualizationMap, datasourceMap }); const { instance, lensStore } = await prepareAndMountComponent(props); - await clickToAddLayer(instance, layerTypes.ANNOTATIONS); + await clickToAddLayer(instance, LayerTypes.ANNOTATIONS); expect(lensStore.dispatch).toHaveBeenCalledTimes(1); expect(visualizationMap.testVis.setDimension).toHaveBeenCalledWith({ @@ -478,9 +479,9 @@ describe('ConfigPanel', () => { const visualizationMap = mockVisualizationMap(); visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ - { type: layerTypes.DATA, label: 'Data Layer' }, + { type: LayerTypes.DATA, label: 'Data Layer' }, { - type: layerTypes.REFERENCELINE, + type: LayerTypes.REFERENCELINE, label: 'Reference layer', }, ]); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx index cd74119bbe31..9d71e74eff47 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx @@ -64,17 +64,20 @@ export function DimensionContainer({ }, [handleClose]); useEffect(() => { - if (isOpen) { - document.body.classList.add('lnsBody--overflowHidden'); - } else { - document.body.classList.remove('lnsBody--overflowHidden'); - } + document.body.classList.toggle('lnsBody--overflowHidden', isOpen); return () => { + if (isOpen) { + setFocusTrapIsEnabled(false); + } document.body.classList.remove('lnsBody--overflowHidden'); }; - }); + }, [isOpen]); + + if (!isOpen) { + return null; + } - return isOpen ? ( + return (
- ) : null; + ); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/remove_layer_action.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/remove_layer_action.tsx index 58a4248b5185..5156fd7efd75 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/remove_layer_action.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/remove_layer_action.tsx @@ -22,9 +22,10 @@ import { import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { LayerAction, Visualization } from '../../../../types'; import { LOCAL_STORAGE_LENS_KEY } from '../../../../settings_storage'; -import { type LayerType, layerTypes } from '../../../..'; +import type { LayerType } from '../../../../../common/types'; interface RemoveLayerAction { execute: () => void; @@ -54,11 +55,11 @@ const getButtonCopy = (layerType: LayerType, canBeRemoved?: boolean, isOnlyLayer let ariaLabel; const layerTypeCopy = - layerType === layerTypes.DATA + layerType === LayerTypes.DATA ? i18n.translate('xpack.lens.modalTitle.layerType.data', { defaultMessage: 'visualization', }) - : layerType === layerTypes.ANNOTATIONS + : layerType === LayerTypes.ANNOTATIONS ? i18n.translate('xpack.lens.modalTitle.layerType.annotation', { defaultMessage: 'annotations', }) @@ -80,9 +81,9 @@ const getButtonCopy = (layerType: LayerType, canBeRemoved?: boolean, isOnlyLayer modalDesc = modalDescClear; } - if (layerType === layerTypes.ANNOTATIONS) { + if (layerType === LayerTypes.ANNOTATIONS) { modalDesc = modalDescAnnotation; - } else if (layerType === layerTypes.REFERENCELINE) { + } else if (layerType === LayerTypes.REFERENCELINE) { modalDesc = modalDescRefLine; } @@ -200,7 +201,7 @@ const RemoveConfirmModal = ({ export const getRemoveLayerAction = (props: RemoveLayerAction): LayerAction => { const { ariaLabel, modalTitle, modalDesc } = getButtonCopy( - props.layerType || layerTypes.DATA, + props.layerType || LayerTypes.DATA, !!props.activeVisualization.removeLayer, props.isOnlyLayer ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index 21cab9188999..c96a022f4aed 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { Datatable } from '@kbn/expressions-plugin/common'; +import type { Datatable } from '@kbn/expressions-plugin/common'; import type { PaletteOutput } from '@kbn/coloring'; -import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; +import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { showMemoizedErrorNotification } from '../../lens_ui_errors'; -import { +import type { Visualization, Datasource, TableSuggestion, @@ -20,8 +21,8 @@ import { Suggestion, DatasourceLayers, } from '../../types'; -import { DragDropIdentifier } from '../../drag_drop'; -import { LayerType, layerTypes } from '../../../common'; +import type { DragDropIdentifier } from '../../drag_drop'; +import type { LayerType } from '../../../common'; import { getLayerType } from './config_panel/add_layer'; import { LensDispatch, @@ -83,7 +84,7 @@ export function getSuggestions({ }, {} as Record); const isLayerSupportedByVisualization = (layerId: string, supportedTypes: LayerType[]) => - supportedTypes.includes(layerTypesMap[layerId] ?? layerTypes.DATA); + supportedTypes.includes(layerTypesMap[layerId] ?? LayerTypes.DATA); // Collect all table suggestions from available datasources const datasourceTableSuggestions = datasources.flatMap(([datasourceId, datasource]) => { @@ -112,14 +113,14 @@ export function getSuggestions({ dataSourceSuggestions = datasource.getDatasourceSuggestionsForField( datasourceState, field, - (layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]), // a field dragged to workspace should added to data layer + (layerId) => isLayerSupportedByVisualization(layerId, [LayerTypes.DATA]), // a field dragged to workspace should added to data layer dataViews.indexPatterns ); } else { dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState( datasourceState, dataViews.indexPatterns, - (layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]), + (layerId) => isLayerSupportedByVisualization(layerId, [LayerTypes.DATA]), activeData ); } diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 28e987d6dbf4..8da6ca0e6d5c 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -10,7 +10,15 @@ import React from 'react'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { render, unmountComponentAtNode } from 'react-dom'; -import type { DataViewBase, EsQueryConfig, Filter, Query, TimeRange } from '@kbn/es-query'; +import { + DataViewBase, + EsQueryConfig, + Filter, + Query, + AggregateQuery, + TimeRange, + isOfQueryType, +} from '@kbn/es-query'; import type { PaletteOutput } from '@kbn/coloring'; import { DataPublicPluginStart, @@ -153,7 +161,7 @@ export interface ViewUnderlyingDataArgs { indexPatternId: string; timeRange: TimeRange; filters: Filter[]; - query: Query | undefined; + query: Query | AggregateQuery | undefined; columns: string[]; } @@ -203,9 +211,21 @@ function getViewUnderlyingDataArgs({ if (error || !meta) { return; } + const luceneOrKuery: Query[] = []; + const aggregateQuery: AggregateQuery[] = []; + + if (Array.isArray(query)) { + query.forEach((q) => { + if (isOfQueryType(q)) { + luceneOrKuery.push(q); + } else { + aggregateQuery.push(q); + } + }); + } const { filters: newFilters, query: newQuery } = combineQueryAndFilters( - query as Query, + luceneOrKuery.length > 0 ? luceneOrKuery : (query as Query), filters, meta, dataViews, @@ -216,7 +236,7 @@ function getViewUnderlyingDataArgs({ indexPatternId: meta.id, timeRange, filters: newFilters, - query: newQuery, + query: aggregateQuery.length > 0 ? aggregateQuery[0] : newQuery, columns: meta.columns, }; } diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts index d96c2aee26c0..cef9e7d698fa 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts @@ -23,16 +23,16 @@ import { IContainer, ErrorEmbeddable, } from '@kbn/embeddable-plugin/public'; -import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { Start as InspectorStart } from '@kbn/inspector-plugin/public'; +import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { Start as InspectorStart } from '@kbn/inspector-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import { LensByReferenceInput, LensEmbeddableInput } from './embeddable'; -import { Document } from '../persistence/saved_object_store'; -import { LensAttributeService } from '../lens_attribute_service'; +import type { LensByReferenceInput, LensEmbeddableInput } from './embeddable'; +import type { Document } from '../persistence/saved_object_store'; +import type { LensAttributeService } from '../lens_attribute_service'; import { DOC_TYPE } from '../../common/constants'; -import { ErrorMessage } from '../editor_frame_service/types'; +import type { ErrorMessage } from '../editor_frame_service/types'; import { extract, inject } from '../../common/embeddable_factory'; -import { DatasourceMap, VisualizationMap } from '../types'; +import type { DatasourceMap, VisualizationMap } from '../types'; export interface LensEmbeddableStartServices { data: DataPublicPluginStart; diff --git a/x-pack/plugins/lens/public/expressions.ts b/x-pack/plugins/lens/public/expressions.ts index a5f193d63e4f..ef12b43bec71 100644 --- a/x-pack/plugins/lens/public/expressions.ts +++ b/x-pack/plugins/lens/public/expressions.ts @@ -6,19 +6,23 @@ */ import type { ExpressionsSetup } from '@kbn/expressions-plugin/public'; + import { getDatatable } from '../common/expressions/datatable/datatable'; import { datatableColumn } from '../common/expressions/datatable/datatable_column'; import { mapToColumns } from '../common/expressions/map_to_columns/map_to_columns'; import { formatColumn } from '../common/expressions/format_column'; import { counterRate } from '../common/expressions/counter_rate'; import { getTimeScale } from '../common/expressions/time_scale/time_scale'; -import { collapse } from '../common/expressions'; +import { collapse } from '../common/expressions/collapse'; + +type TimeScaleArguments = Parameters; export const setupExpressions = ( expressions: ExpressionsSetup, formatFactory: Parameters[0], - getDatatableUtilities: Parameters[0], - getTimeZone: Parameters[1] + getDatatableUtilities: TimeScaleArguments[0], + getTimeZone: TimeScaleArguments[1], + getForceNow: TimeScaleArguments[2] ) => { [ collapse, @@ -27,6 +31,6 @@ export const setupExpressions = ( mapToColumns, datatableColumn, getDatatable(formatFactory), - getTimeScale(getDatatableUtilities, getTimeZone), + getTimeScale(getDatatableUtilities, getTimeZone, getForceNow), ].forEach((expressionFn) => expressions.registerFunction(expressionFn)); }; diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 776d8731d7ea..9efe67c4283e 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { LensPlugin } from './plugin'; export type { @@ -101,9 +102,11 @@ export type { ReferenceLineLayerArgs, ReferenceLineLayerConfig, } from '@kbn/expression-xy-plugin/common'; + export type { LensEmbeddableInput, LensSavedObjectAttributes, Embeddable } from './embeddable'; -export { layerTypes } from '../common'; +/** @deprecated Please use LayerTypes from @kbn/expression-xy-plugin **/ +export const layerTypes = LayerTypes; export type { LensPublicStart, LensPublicSetup } from './plugin'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 1ce184e623e7..4107cd0c14fa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -329,7 +329,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ const fieldInfoUnavailable = existenceFetchFailed || existenceFetchTimeout || currentIndexPattern?.hasRestrictions; - const editPermission = indexPatternFieldEditor.userPermissions.editIndexPattern(); + const editPermission = + indexPatternFieldEditor.userPermissions.editIndexPattern() || !currentIndexPattern.isPersisted; const unfilteredFieldGroups: FieldGroups = useMemo(() => { const containsData = (field: IndexPatternField) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 5a975482e625..b4cad9aa7369 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -85,6 +85,10 @@ export interface DimensionEditorProps extends IndexPatternDimensionEditorProps { const operationDisplay = getOperationDisplay(); +function getHelpMessage(flag?: boolean | { helpMessage: string }) { + return flag && typeof flag !== 'boolean' ? flag.helpMessage : null; +} + export function DimensionEditor(props: DimensionEditorProps) { const { selectedColumn, @@ -135,55 +139,58 @@ export function DimensionEditor(props: DimensionEditorProps) { [layerId, setState] ); - const setStateWrapper = ( - setter: - | IndexPatternLayer - | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) - | GenericIndexPatternColumn, - options: { forceRender?: boolean } = {} - ) => { - const layer = state.layers[layerId]; - let hypotethicalLayer: IndexPatternLayer; - if (isColumn(setter)) { - hypotethicalLayer = { - ...layer, - columns: { - ...layer.columns, - [columnId]: setter, - }, - }; - } else { - hypotethicalLayer = typeof setter === 'function' ? setter(state.layers[layerId]) : setter; - } - const isDimensionComplete = Boolean(hypotethicalLayer.columns[columnId]); + const setStateWrapper = useCallback( + ( + setter: + | IndexPatternLayer + | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + | GenericIndexPatternColumn, + options: { forceRender?: boolean } = {} + ) => { + const layer = state.layers[layerId]; + let hypotethicalLayer: IndexPatternLayer; + if (isColumn(setter)) { + hypotethicalLayer = { + ...layer, + columns: { + ...layer.columns, + [columnId]: setter, + }, + }; + } else { + hypotethicalLayer = typeof setter === 'function' ? setter(state.layers[layerId]) : setter; + } + const isDimensionComplete = Boolean(hypotethicalLayer.columns[columnId]); - setState( - (prevState) => { - let outputLayer: IndexPatternLayer; - const prevLayer = prevState.layers[layerId]; - if (isColumn(setter)) { - outputLayer = { - ...prevLayer, - columns: { - ...prevLayer.columns, - [columnId]: setter, - }, - }; - } else { - outputLayer = typeof setter === 'function' ? setter(prevState.layers[layerId]) : setter; + setState( + (prevState) => { + let outputLayer: IndexPatternLayer; + const prevLayer = prevState.layers[layerId]; + if (isColumn(setter)) { + outputLayer = { + ...prevLayer, + columns: { + ...prevLayer.columns, + [columnId]: setter, + }, + }; + } else { + outputLayer = typeof setter === 'function' ? setter(prevState.layers[layerId]) : setter; + } + return mergeLayer({ + state: prevState, + layerId, + newLayer: adjustColumnReferencesForChangedColumn(outputLayer, columnId), + }); + }, + { + isDimensionComplete, + ...options, } - return mergeLayer({ - state: prevState, - layerId, - newLayer: adjustColumnReferencesForChangedColumn(outputLayer, columnId), - }); - }, - { - isDimensionComplete, - ...options, - } - ); - }; + ); + }, + [columnId, layerId, setState, state.layers] + ); const setIsCloseable = (isCloseable: boolean) => { setState((prevState) => ({ ...prevState, isDimensionClosePrevented: !isCloseable })); @@ -605,7 +612,15 @@ export function DimensionEditor(props: DimensionEditorProps) { ...services, }; - const helpButton = ; + const helpButton = ( + + ); const columnsSidebar = [ { @@ -639,6 +654,11 @@ export function DimensionEditor(props: DimensionEditorProps) { + + {i18n.translate('xpack.lens.indexPattern.functionsLabel', { + defaultMessage: 'Functions', + })} + - - {i18n.translate('xpack.lens.indexPattern.functionsLabel', { - defaultMessage: 'Functions', - })} -
} fullWidth @@ -957,6 +972,30 @@ export function DimensionEditor(props: DimensionEditorProps) { [layerId, selectedColumn, props.indexPatterns, state.layers] ); + /** + * Advanced options can cause side effects on other columns (i.e. formulas) + * so before updating the layer the full insertOrReplaceColumn needs to be performed + */ + const updateAdvancedOption = useCallback( + (newLayer) => { + if (selectedColumn) { + setStateWrapper( + // formula need to regenerate from scratch + selectedColumn.operationType === formulaOperationName + ? insertOrReplaceColumn({ + op: selectedColumn.operationType, + layer: newLayer, + columnId, + indexPattern: currentIndexPattern, + visualizationGroups: dimensionGroups, + }) + : newLayer + ); + } + }, + [columnId, currentIndexPattern, dimensionGroups, selectedColumn, setStateWrapper] + ); + const shouldDisplayAdvancedOptions = !isFullscreen && !currentFieldIsInvalid && @@ -1012,7 +1051,7 @@ export function DimensionEditor(props: DimensionEditorProps) { selectedColumn={selectedColumn} columnId={columnId} layer={state.layers[layerId]} - updateLayer={setStateWrapper} + updateLayer={updateAdvancedOption} /> ) : null, }, @@ -1024,13 +1063,8 @@ export function DimensionEditor(props: DimensionEditorProps) { selectedColumn={selectedColumn} columnId={columnId} layer={state.layers[layerId]} - updateLayer={setStateWrapper} - helpMessage={ - selectedOperationDefinition.filterable && - typeof selectedOperationDefinition.filterable !== 'boolean' - ? selectedOperationDefinition.filterable.helpMessage - : null - } + updateLayer={updateAdvancedOption} + helpMessage={getHelpMessage(selectedOperationDefinition.filterable)} /> ) : null, }, @@ -1042,7 +1076,9 @@ export function DimensionEditor(props: DimensionEditorProps) { columnId={columnId} indexPattern={currentIndexPattern} layer={state.layers[layerId]} - updateLayer={setStateWrapper} + updateLayer={updateAdvancedOption} + skipLabelUpdate={hasFormula} + helpMessage={getHelpMessage(selectedOperationDefinition.canReduceTimeRange)} /> ) : null, }, @@ -1061,7 +1097,7 @@ export function DimensionEditor(props: DimensionEditorProps) { selectedColumn={selectedColumn} columnId={columnId} layer={state.layers[layerId]} - updateLayer={setStateWrapper} + updateLayer={updateAdvancedOption} activeData={props.activeData} layerId={layerId} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index c7c0ff5d205c..41dd1df5bcd9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -32,6 +32,7 @@ import { CoreStart, } from '@kbn/core/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { generateId } from '../../id_generator'; import { IndexPatternPrivateState } from '../types'; import { @@ -49,7 +50,6 @@ import { TimeShift } from './time_shift'; import { ReducedTimeRange } from './reduced_time_range'; import { DimensionEditor } from './dimension_editor'; import { AdvancedOptions } from './advanced_options'; -import { layerTypes } from '../../../common'; jest.mock('./reference_editor', () => ({ ReferenceEditor: () => null, @@ -210,7 +210,7 @@ describe('IndexPatternDimensionEditorPanel', () => { dateRange: { fromDate: 'now-1d', toDate: 'now' }, columnId: 'col1', layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, uniqueLabel: 'stuff', filterOperations: () => true, storage: {} as IStorageWrapper, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx index e38d48034622..e31c5abffc98 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx @@ -7,17 +7,18 @@ import React, { memo, useMemo } from 'react'; import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from '@kbn/core/public'; -import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DatasourceDimensionTriggerProps, DatasourceDimensionEditorProps } from '../../types'; -import { GenericIndexPatternColumn } from '../indexpattern'; +import type { DatasourceDimensionTriggerProps, DatasourceDimensionEditorProps } from '../../types'; +import type { GenericIndexPatternColumn } from '../indexpattern'; import { isColumnInvalid } from '../utils'; -import { IndexPatternPrivateState } from '../types'; +import type { IndexPatternPrivateState } from '../types'; import { DimensionEditor } from './dimension_editor'; -import { DateRange, layerTypes } from '../../../common'; +import type { DateRange } from '../../../common'; import { getOperationSupportMatrix } from './operation_support'; import { DimensionTrigger } from '../../shared_components/dimension_trigger'; @@ -94,7 +95,7 @@ export const IndexPatternDimensionEditorComponent = function IndexPatternDimensi return ( void; indexPattern: IndexPattern; + helpMessage: string | null; + skipLabelUpdate?: boolean; }) { const [localValue, setLocalValue] = useState(selectedColumn.reducedTimeRange); useEffect(() => { @@ -99,6 +105,9 @@ export function ReducedTimeRange({ }, ]; } + const label = i18n.translate('xpack.lens.indexPattern.reducedTimeRange.label', { + defaultMessage: 'Reduced time range', + }); return (
@@ -106,9 +115,25 @@ export function ReducedTimeRange({ display="rowCompressed" fullWidth data-test-subj="indexPattern-dimension-reducedTimeRange-row" - label={i18n.translate('xpack.lens.indexPattern.reducedTimeRange.label', { - defaultMessage: 'Reduced time range', - })} + label={ + helpMessage ? ( + <> + {label}{' '} + + + ) : ( + label + ) + } helpText={i18n.translate('xpack.lens.indexPattern.reducedTimeRange.help', { defaultMessage: 'Reduces the time range specified in the global time filter from the end of the global time filter.', @@ -144,14 +169,14 @@ export function ReducedTimeRange({ onCreateOption={(val) => { const parsedVal = parseTimeShift(val); if (!isInvalid(parsedVal)) { - updateLayer(setReducedTimeRange(columnId, layer, val)); + updateLayer(setReducedTimeRange(columnId, layer, val, skipLabelUpdate)); } else { setLocalValue(val); } }} onChange={(choices) => { if (choices.length === 0) { - updateLayer(setReducedTimeRange(columnId, layer, '')); + updateLayer(setReducedTimeRange(columnId, layer, '', skipLabelUpdate)); setLocalValue(''); return; } @@ -159,7 +184,7 @@ export function ReducedTimeRange({ const choice = choices[0].value as string; const parsedVal = parseTimeShift(choice); if (!isInvalid(parsedVal)) { - updateLayer(setReducedTimeRange(columnId, layer, choice)); + updateLayer(setReducedTimeRange(columnId, layer, choice, skipLabelUpdate)); } else { setLocalValue(choice); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx index 536123956ce8..791d3b5846f2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx @@ -13,7 +13,6 @@ import { EuiFlexItem, EuiFlexGroup, } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; import React from 'react'; import { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/document_field.ts b/x-pack/plugins/lens/public/indexpattern_datasource/document_field.ts index 208b9821601e..e6ef0fcfdc0b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/document_field.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/document_field.ts @@ -9,14 +9,17 @@ import { i18n } from '@kbn/i18n'; import { DOCUMENT_FIELD_NAME } from '../../common'; import type { IndexPatternField } from '../types'; +const customLabel = i18n.translate('xpack.lens.indexPattern.records', { + defaultMessage: 'Records', +}); + /** * This is a special-case field which allows us to perform * document-level operations such as count. */ export const documentField: IndexPatternField = { - displayName: i18n.translate('xpack.lens.indexPattern.records', { - defaultMessage: 'Records', - }), + displayName: customLabel, + customLabel, name: DOCUMENT_FIELD_NAME, type: 'document', aggregatable: true, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss index a309e37938cd..873f5bdcbdf5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss @@ -54,7 +54,3 @@ min-width: 260px; max-width: 300px; } - -.lnsFieldItem__fieldPanelTitle { - text-transform: none; -} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index 64de29b0691c..19b3f9e31326 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -23,9 +23,9 @@ import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; -import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { loadFieldStats } from '@kbn/unified-field-list-plugin/public/services/field_stats'; -import { FieldStats } from '@kbn/unified-field-list-plugin/public'; +import { FieldStats, FieldVisualizeButton } from '@kbn/unified-field-list-plugin/public'; import { DOCUMENT_FIELD_NAME } from '../../common'; import { LensFieldIcon } from '../shared_components'; @@ -69,6 +69,14 @@ const InnerFieldItemWrapper: React.FC = (props) => { ); }; +async function getComponent(props: FieldItemProps) { + const instance = await mountWithIntl(); + // wait for lazy modules + await new Promise((resolve) => setTimeout(resolve, 0)); + await instance.update(); + return instance; +} + describe('IndexPattern Field Item', () => { let defaultProps: FieldItemProps; let indexPattern: IndexPattern; @@ -122,6 +130,13 @@ describe('IndexPattern Field Item', () => { aggregatable: true, searchable: true, }, + { + name: 'geo.coordinates', + displayName: 'geo.coordinates', + type: 'geo_shape', + aggregatable: true, + searchable: true, + }, documentField, ], } as IndexPattern; @@ -173,8 +188,8 @@ describe('IndexPattern Field Item', () => { (loadFieldStats as jest.Mock).mockImplementation(() => Promise.resolve({})); }); - it('should display displayName of a field', () => { - const wrapper = mountWithIntl(); + it('should display displayName of a field', async () => { + const wrapper = await getComponent(defaultProps); // Using .toContain over .toEqual because this element includes text from // which can't be seen, but shows in the text content @@ -183,13 +198,11 @@ describe('IndexPattern Field Item', () => { ); }); - it('should show gauge icon for gauge fields', () => { - const wrapper = mountWithIntl( - - ); + it('should show gauge icon for gauge fields', async () => { + const wrapper = await getComponent({ + ...defaultProps, + field: { ...defaultProps.field, timeSeriesMetricType: 'gauge' }, + }); // Using .toContain over .toEqual because this element includes text from // which can't be seen, but shows in the text content @@ -198,9 +211,11 @@ describe('IndexPattern Field Item', () => { it('should render edit field button if callback is set', async () => { const editFieldSpy = jest.fn(); - const wrapper = mountWithIntl( - - ); + const wrapper = await getComponent({ + ...defaultProps, + editField: editFieldSpy, + hideDetails: true, + }); await clickField(wrapper, 'bytes'); await wrapper.update(); const popoverContent = wrapper.find(EuiPopover).prop('children'); @@ -210,7 +225,7 @@ describe('IndexPattern Field Item', () => { {popoverContent as ReactElement} ) - .find('[data-test-subj="lnsFieldListPanelEdit"]') + .find('[data-test-subj="fieldPopoverHeader_editField-bytes"]') .first() .simulate('click'); }); @@ -219,14 +234,12 @@ describe('IndexPattern Field Item', () => { it('should not render edit field button for document field', async () => { const editFieldSpy = jest.fn(); - const wrapper = mountWithIntl( - - ); + const wrapper = await getComponent({ + ...defaultProps, + field: documentField, + editField: editFieldSpy, + hideDetails: true, + }); await clickField(wrapper, documentField.name); await wrapper.update(); const popoverContent = wrapper.find(EuiPopover).prop('children'); @@ -236,12 +249,20 @@ describe('IndexPattern Field Item', () => { {popoverContent as ReactElement} ) - .find('[data-test-subj="lnsFieldListPanelEdit"]') + .find('[data-test-subj="fieldPopoverHeader_editField-bytes"]') .exists() ).toBeFalsy(); }); it('should pass add filter callback and pass result to filter manager', async () => { + let resolveFunction: (arg: unknown) => void; + + (loadFieldStats as jest.Mock).mockImplementation(() => { + return new Promise((resolve) => { + resolveFunction = resolve; + }); + }); + const field = { name: 'test', displayName: 'testLabel', @@ -252,25 +273,37 @@ describe('IndexPattern Field Item', () => { }; const editFieldSpy = jest.fn(); - const wrapper = mountWithIntl( - - ); + const wrapper = await getComponent({ + ...defaultProps, + field, + editField: editFieldSpy, + }); + await clickField(wrapper, field.name); await wrapper.update(); - const popoverContent = wrapper.find(EuiPopover).prop('children'); - const instance = mountWithIntl( - - {popoverContent as ReactElement} - - ); - const onAddFilter = instance.find(FieldStats).prop('onAddFilter'); - onAddFilter!(field as DataViewField, 'abc', '+'); + + await act(async () => { + resolveFunction!({ + totalDocuments: 4633, + sampledDocuments: 4633, + sampledValues: 4633, + topValues: { + buckets: [{ count: 147, key: 'abc' }], + }, + }); + }); + + await wrapper.update(); + + wrapper.find(`[data-test-subj="plus-${field.name}-abc"]`).first().simulate('click'); + expect(mockedServices.data.query.filterManager.addFilters).toHaveBeenCalledWith([ expect.objectContaining({ query: { match_phrase: { test: 'abc' } } }), ]); }); it('should request field stats every time the button is clicked', async () => { + const dataViewField = new DataViewField(defaultProps.field); let resolveFunction: (arg: unknown) => void; (loadFieldStats as jest.Mock).mockImplementation(() => { @@ -279,7 +312,7 @@ describe('IndexPattern Field Item', () => { }); }); - const wrapper = mountWithIntl(); + const wrapper = await getComponent(defaultProps); await clickField(wrapper, 'bytes'); @@ -299,7 +332,7 @@ describe('IndexPattern Field Item', () => { }, fromDate: 'now-7d', toDate: 'now', - field: defaultProps.field, + field: dataViewField, }); expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true); @@ -384,14 +417,15 @@ describe('IndexPattern Field Item', () => { }, fromDate: 'now-14d', toDate: 'now-7d', - field: defaultProps.field, + field: dataViewField, }); }); it('should not request field stats for document field', async () => { - const wrapper = await mountWithIntl( - - ); + const wrapper = await getComponent({ + ...defaultProps, + field: documentField, + }); await clickField(wrapper, DOCUMENT_FIELD_NAME); @@ -404,18 +438,16 @@ describe('IndexPattern Field Item', () => { }); it('should not request field stats for range fields', async () => { - const wrapper = await mountWithIntl( - - ); + const wrapper = await getComponent({ + ...defaultProps, + field: { + name: 'ip_range', + displayName: 'ip_range', + type: 'ip_range', + aggregatable: true, + searchable: true, + }, + }); await clickField(wrapper, 'ip_range'); @@ -425,6 +457,30 @@ describe('IndexPattern Field Item', () => { expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true); expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); expect(wrapper.find(FieldStats).text()).toBe('Analysis is not available for this field.'); + expect(wrapper.find(FieldVisualizeButton).exists()).toBeFalsy(); + }); + + it('should not request field stats for geo fields but render Visualize button', async () => { + const wrapper = await getComponent({ + ...defaultProps, + field: { + name: 'geo.coordinates', + displayName: 'geo.coordinates', + type: 'geo_shape', + aggregatable: true, + searchable: true, + }, + }); + + await clickField(wrapper, 'geo.coordinates'); + + await wrapper.update(); + + expect(loadFieldStats).toHaveBeenCalled(); + expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true); + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); + expect(wrapper.find(FieldStats).text()).toBe('Analysis is not available for this field.'); + expect(wrapper.find(FieldVisualizeButton).exists()).toBeTruthy(); }); it('should display Explore in discover button', async () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 6434af979028..17b1ce35f38b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -8,36 +8,30 @@ import './field_item.scss'; import React, { useCallback, useState, useMemo } from 'react'; -import { - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiIconTip, - EuiPopover, - EuiPopoverTitle, - EuiPopoverFooter, - EuiText, - EuiTitle, - EuiToolTip, - EuiButton, -} from '@elastic/eui'; +import { EuiIconTip, EuiText, EuiButton, EuiPopoverFooter } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { FieldButton } from '@kbn/react-field'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { EuiHighlight } from '@elastic/eui'; import { Filter, Query } from '@kbn/es-query'; -import { DataViewField } from '@kbn/data-views-plugin/common'; +import { DataViewField, type DataView } from '@kbn/data-views-plugin/common'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { AddFieldFilterHandler, FieldStats } from '@kbn/unified-field-list-plugin/public'; +import { + AddFieldFilterHandler, + FieldStats, + FieldPopover, + FieldPopoverHeader, + FieldPopoverVisualize, +} from '@kbn/unified-field-list-plugin/public'; import { generateFilters, getEsQueryConfig } from '@kbn/data-plugin/public'; -import { DragDrop, DragDropIdentifier } from '../drag_drop'; -import { DatasourceDataPanelProps, DataType, DraggedField } from '../types'; +import { APP_ID } from '../../common/constants'; +import { DragDrop } from '../drag_drop'; +import { DatasourceDataPanelProps, DataType } from '../types'; import { DOCUMENT_FIELD_NAME } from '../../common'; import type { IndexPattern, IndexPatternField } from '../types'; import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; -import { VisualizeGeoFieldButton } from './visualize_geo_field_button'; import type { LensAppServices } from '../app_plugin/types'; import { debouncedComponent } from '../debounced_component'; import { getFieldType } from './pure_utils'; @@ -81,50 +75,64 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { itemIndex, groupIndex, dropOntoWorkspace, + hasSuggestionForField, editField, removeField, } = props; + const dataViewField = useMemo(() => new DataViewField(field), [field]); + const services = useKibana().services; + const filterManager = services?.data?.query?.filterManager; const [infoIsOpen, setOpen] = useState(false); - const closeAndEdit = useMemo( + const togglePopover = useCallback(() => { + setOpen((value) => !value); + }, [setOpen]); + + const closePopover = useCallback(() => { + setOpen(false); + }, [setOpen]); + + const addFilterAndClose: AddFieldFilterHandler | undefined = useMemo( () => - editField + filterManager + ? (clickedField, values, operation) => { + closePopover(); + const newFilters = generateFilters( + filterManager, + clickedField, + values, + operation, + indexPattern + ); + filterManager.addFilters(newFilters); + } + : undefined, + [indexPattern, filterManager, closePopover] + ); + + const editFieldAndClose = useMemo( + () => + editField && dataViewField.name !== DOCUMENT_FIELD_NAME ? (name: string) => { + closePopover(); editField(name); - setOpen(false); } : undefined, - [editField, setOpen] + [editField, closePopover, dataViewField.name] ); - const closeAndRemove = useMemo( + const removeFieldAndClose = useMemo( () => removeField ? (name: string) => { + closePopover(); removeField(name); - setOpen(false); } : undefined, - [removeField, setOpen] - ); - - const dropOntoWorkspaceAndClose = useCallback( - (droppedField: DragDropIdentifier) => { - dropOntoWorkspace(droppedField); - setOpen(false); - }, - [dropOntoWorkspace, setOpen] + [removeField, closePopover] ); - function togglePopover() { - setOpen(!infoIsOpen); - } - - const onDragStart = useCallback(() => { - setOpen(false); - }, [setOpen]); - const value = useMemo( () => ({ field, @@ -137,6 +145,16 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { }), [field, indexPattern.id, itemIndex] ); + + const dropOntoWorkspaceAndClose = useCallback(() => { + closePopover(); + dropOntoWorkspace(value); + }, [dropOntoWorkspace, closePopover, value]); + + const onDragStart = useCallback(() => { + setOpen(false); + }, [setOpen]); + const order = useMemo(() => [0, groupIndex, itemIndex], [groupIndex, itemIndex]); const lensFieldIcon = ; @@ -162,12 +180,15 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { size="s" /> ); + return (
  • - ('.application') || undefined} button={ @@ -206,152 +227,67 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { /> } - isOpen={infoIsOpen} - closePopover={() => setOpen(false)} - anchorPosition="rightUp" - panelClassName="lnsFieldItem__fieldPanel" - initialFocus=".lnsFieldItem__fieldPanel" - > - {infoIsOpen && ( - - )} - + renderHeader={() => { + const canAddToWorkspace = hasSuggestionForField(value); + const buttonTitle = canAddToWorkspace + ? i18n.translate('xpack.lens.indexPattern.moveToWorkspace', { + defaultMessage: 'Add {field} to workspace', + values: { + field: value.field.name, + }, + }) + : i18n.translate('xpack.lens.indexPattern.moveToWorkspaceDisabled', { + defaultMessage: + "This field can't be added to the workspace automatically. You can still use it directly in the configuration panel.", + }); + + return ( + + ); + }} + renderContent={ + !hideDetails + ? () => ( + + ) + : undefined + } + />
  • ); }; export const FieldItem = debouncedComponent(InnerFieldItem); -function FieldPanelHeader({ - indexPatternId, - field, - hasSuggestionForField, - dropOntoWorkspace, - editField, - removeField, -}: { - field: IndexPatternField; - indexPatternId: string; - hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; - dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; - editField?: (name: string) => void; - removeField?: (name: string) => void; -}) { - const draggableField = { - indexPatternId, - id: field.name, - field, - humanData: { - label: field.displayName, - }, - }; - - return ( - - - -
    {field.displayName}
    -
    -
    - - - {editField && field.name !== DOCUMENT_FIELD_NAME && ( - - - editField(field.name)} - iconType="pencil" - data-test-subj="lnsFieldListPanelEdit" - aria-label={i18n.translate('xpack.lens.indexPattern.editFieldLabel', { - defaultMessage: 'Edit data view field', - })} - /> - - - )} - {removeField && field.runtime && ( - - - removeField(field.name)} - iconType="trash" - data-test-subj="lnsFieldListPanelRemove" - color="danger" - aria-label={i18n.translate('xpack.lens.indexPattern.removeFieldLabel', { - defaultMessage: 'Remove data view field', - })} - /> - - - )} -
    - ); -} - -function FieldItemPopoverContents(props: FieldItemProps) { - const { - query, - filters, - indexPattern, - field, - dateRange, - dropOntoWorkspace, - editField, - removeField, - hasSuggestionForField, - hideDetails, - uiActions, - core, - } = props; +function FieldItemPopoverContents( + props: FieldItemProps & { + dataViewField: DataViewField; + onAddFilter: AddFieldFilterHandler | undefined; + } +) { + const { query, filters, indexPattern, dataViewField, dateRange, core, onAddFilter, uiActions } = + props; const services = useKibana().services; - const onAddFilter: AddFieldFilterHandler = useCallback( - (clickedField, values, operation) => { - const filterManager = services.data.query.filterManager; - const newFilters = generateFilters( - filterManager, - clickedField, - values, - operation, - indexPattern - ); - filterManager.addFilters(newFilters); - }, - [indexPattern, services.data.query.filterManager] - ); - - const panelHeader = ( - - ); - const exploreInDiscover = useMemo(() => { const meta = { id: indexPattern.id, - columns: [field.name], + columns: [dataViewField.name], filters: { enabled: { lucene: [], @@ -380,15 +316,10 @@ function FieldItemPopoverContents(props: FieldItemProps) { query: newQuery, columns: meta.columns, }); - }, [field.name, filters, indexPattern, query, services]); - - if (hideDetails) { - return panelHeader; - } + }, [dataViewField.name, filters, indexPattern, query, services]); return ( <> - {panelHeader} { - if (field.type === 'geo_point' || field.type === 'geo_shape') { - return ( - <> - {params.element} - - - - - - ); - } - if (params?.noDataFound) { // TODO: should we replace this with a default message "Analysis is not available for this field?" const isUsingSampling = core.uiSettings.get('lens:useFieldExistenceSampling'); @@ -439,58 +354,32 @@ function FieldItemPopoverContents(props: FieldItemProps) { return params.element; }} /> - {exploreInDiscover && field.type !== 'geo_point' && field.type !== 'geo_shape' && ( + + {dataViewField.type === 'geo_point' || dataViewField.type === 'geo_shape' ? ( + indexPattern.spec } as unknown as DataView} + originatingApp={APP_ID} + uiActions={uiActions} + buttonProps={{ + 'data-test-subj': `lensVisualize-GeoField-${dataViewField.name}`, + }} + /> + ) : exploreInDiscover ? ( {i18n.translate('xpack.lens.indexPattern.fieldExploreInDiscover', { defaultMessage: 'Explore values in Discover', })} - )} + ) : null} ); } - -const DragToWorkspaceButton = ({ - field, - dropOntoWorkspace, - isEnabled, -}: { - field: DraggedField; - dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; - isEnabled: boolean; -}) => { - const buttonTitle = isEnabled - ? i18n.translate('xpack.lens.indexPattern.moveToWorkspace', { - defaultMessage: 'Add {field} to workspace', - values: { - field: field.field.name, - }, - }) - : i18n.translate('xpack.lens.indexPattern.moveToWorkspaceDisabled', { - defaultMessage: - "This field can't be added to the workspace automatically. You can still use it directly in the configuration panel.", - }); - - return ( - - - { - dropOntoWorkspace(field); - }} - /> - - - ); -}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.test.ts index 1dacb334413f..1d27c0db935c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.test.ts @@ -8,7 +8,7 @@ import { checkReferences, checkForDataLayerType } from './utils'; import { operationDefinitionMap } from '..'; import { createMockedFullReference } from '../../mocks'; -import { layerTypes } from '../../../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { DateHistogramIndexPatternColumn } from '../date_histogram'; // Mock prevents issue with circular loading @@ -22,7 +22,7 @@ describe('utils', () => { describe('checkForDataLayerType', () => { it('should return an error if the layer is of the wrong type', () => { - expect(checkForDataLayerType(layerTypes.REFERENCELINE, 'Operation')).toEqual([ + expect(checkForDataLayerType(LayerTypes.REFERENCELINE, 'Operation')).toEqual([ 'Operation is disabled for this type of layer.', ]); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts index 04a8f554be7d..5b33fb071753 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts @@ -8,8 +8,9 @@ import { i18n } from '@kbn/i18n'; import type { AstFunction } from '@kbn/interpreter'; import memoizeOne from 'memoize-one'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { IndexPattern } from '../../../../types'; -import { LayerType, layerTypes } from '../../../../../common'; +import type { LayerType } from '../../../../../common'; import type { TimeScaleUnit } from '../../../../../common/expressions'; import type { IndexPatternLayer } from '../../../types'; import { adjustTimeScaleLabelSuffix } from '../../time_scale_utils'; @@ -33,7 +34,7 @@ export const buildLabelFunction = }; export function checkForDataLayerType(layerType: LayerType, name: string) { - if (layerType === layerTypes.REFERENCELINE) { + if (layerType === LayerTypes.REFERENCELINE) { return [ i18n.translate('xpack.lens.indexPattern.calculations.layerDataType', { defaultMessage: '{name} is disabled for this type of layer.', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index b911ac5394a2..1f67eebf3541 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -11,9 +11,9 @@ import { euiThemeVars } from '@kbn/ui-theme'; import { EuiSwitch, EuiText } from '@elastic/eui'; import { AggFunctionsMapping } from '@kbn/data-plugin/public'; import { buildExpressionFunction } from '@kbn/expressions-plugin/public'; -import { TimeScaleUnit } from '../../../../common/expressions'; -import { OperationDefinition, ParamEditorProps } from '.'; -import { FieldBasedIndexPatternColumn, ValueFormatConfig } from './column_types'; +import type { TimeScaleUnit } from '../../../../common/expressions'; +import type { OperationDefinition, ParamEditorProps } from '.'; +import type { FieldBasedIndexPatternColumn, ValueFormatConfig } from './column_types'; import type { IndexPatternField } from '../../../types'; import { getInvalidFieldMessage, @@ -50,7 +50,7 @@ function ofName( timeScale: string | undefined, reducedTimeRange: string | undefined ) { - if (field?.customLabel) { + if (field?.customLabel && field?.type !== 'document') { return field.customLabel; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts index d90a021a7cb1..79e77c3275ea 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts @@ -11,7 +11,7 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../../../mocks'; import { GenericOperationDefinition } from '../..'; -import type { OperationMetadata, IndexPatternField } from '../../../../../types'; +import type { IndexPatternField, OperationMetadata } from '../../../../../types'; import { tinymathFunctions } from '../util'; import { getSignatureHelp, @@ -21,9 +21,10 @@ import { offsetToRowColumn, getInfoAtZeroIndexedPosition, } from './math_completion'; +import { createOperationDefinitionMock } from '../mocks/operation_mocks'; -const buildGenericColumn = (type: string) => { - return ({ field }: { field?: IndexPatternField }) => { +const buildGenericColumn = (type: string) => { + return (({ field }: { field?: IndexPatternField }) => { return { label: type, dataType: 'number', @@ -33,44 +34,39 @@ const buildGenericColumn = (type: string) => { scale: 'ratio', timeScale: false, }; - }; + }) as unknown as Extract['buildColumn']; }; -const numericOperation = () => ({ dataType: 'number', isBucketed: false }); -const stringOperation = () => ({ dataType: 'string', isBucketed: true }); +// Mind the OperationMetadata shape here, it is very important for the field suggestions; +// internally they are serialized and compared as strings +const numericOperation = (): OperationMetadata => ({ dataType: 'number', isBucketed: false }); +const stringOperation = (): OperationMetadata => ({ dataType: 'string', isBucketed: true }); // Only one of each type is needed const operationDefinitionMap: Record = { - sum: { - type: 'sum', - input: 'field', - buildColumn: buildGenericColumn('sum'), - getPossibleOperationForField: (field: IndexPatternField) => - field.type === 'number' ? numericOperation() : null, + sum: createOperationDefinitionMock('sum', { + getPossibleOperationForField: jest.fn((field: IndexPatternField) => + field.type === 'number' ? numericOperation() : undefined + ), documentation: { section: 'elasticsearch', signature: 'field: string', description: 'description', }, - } as unknown as GenericOperationDefinition, - count: { - type: 'count', - input: 'field', - buildColumn: buildGenericColumn('count'), + }), + count: createOperationDefinitionMock('count', { getPossibleOperationForField: (field: IndexPatternField) => - field.name === '___records___' ? numericOperation() : null, - } as unknown as GenericOperationDefinition, - last_value: { - type: 'last_value', - input: 'field', + field.name === '___records___' ? numericOperation() : undefined, + }), + last_value: createOperationDefinitionMock('last_value', { buildColumn: buildGenericColumn('last_value'), - getPossibleOperationForField: (field: IndexPatternField) => ({ - dataType: field.type, - isBucketed: false, - }), - } as unknown as GenericOperationDefinition, - moving_average: { - type: 'moving_average', + getPossibleOperationForField: (field: IndexPatternField) => + ({ + dataType: field.type, + isBucketed: false, + } as OperationMetadata), + }), + moving_average: createOperationDefinitionMock('moving_average', { input: 'fullReference', requiredReferences: [ { @@ -80,20 +76,21 @@ const operationDefinitionMap: Record = { }, ], operationParams: [{ name: 'window', type: 'number', required: true }], - buildColumn: buildGenericColumn('moving_average'), - getPossibleOperation: numericOperation, - } as unknown as GenericOperationDefinition, - cumulative_sum: { - type: 'cumulative_sum', + buildColumn: buildGenericColumn<'fullReference'>('moving_average'), + }), + cumulative_sum: createOperationDefinitionMock('cumulative_sum', { input: 'fullReference', - buildColumn: buildGenericColumn('cumulative_sum'), - getPossibleOperation: numericOperation, - } as unknown as GenericOperationDefinition, - terms: { - type: 'terms', - input: 'field', - getPossibleOperationForField: stringOperation, - } as unknown as GenericOperationDefinition, + buildColumn: buildGenericColumn<'fullReference'>('cumulative_sum'), + }), + terms: createOperationDefinitionMock( + 'terms', + { + getPossibleOperationForField: stringOperation, + }, + { + scale: 'ordinal', + } + ), }; describe('math completion', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx index e68bb0d3142e..9c5cf27e60a8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx @@ -6,16 +6,21 @@ */ import { createMockedIndexPattern } from '../../../mocks'; -import { formulaOperation, GenericOperationDefinition, GenericIndexPatternColumn } from '..'; -import { FormulaIndexPatternColumn } from './formula'; +import { + formulaOperation, + type GenericOperationDefinition, + type GenericIndexPatternColumn, +} from '..'; +import type { FormulaIndexPatternColumn } from './formula'; import { insertOrReplaceFormulaColumn } from './parse'; import type { IndexPatternLayer } from '../../../types'; -import { IndexPattern, IndexPatternField } from '../../../../types'; +import { IndexPattern } from '../../../../types'; import { tinymathFunctions } from './util'; import { TermsIndexPatternColumn } from '../terms'; import { MovingAverageIndexPatternColumn } from '../calculations'; import { StaticValueIndexPatternColumn } from '../static_value'; import { getFilter } from '../helpers'; +import { createOperationDefinitionMock } from './mocks/operation_mocks'; jest.mock('../../layer_helpers', () => { return { @@ -26,89 +31,41 @@ jest.mock('../../layer_helpers', () => { }; }); -interface PartialColumnParams { - kql?: string; - lucene?: string; - shift?: string; -} - const operationDefinitionMap: Record = { - average: { - input: 'field', - buildColumn: ({ field }: { field: IndexPatternField }) => ({ - label: 'avg', - dataType: 'number', - operationType: 'average', - sourceField: field.name, - isBucketed: false, - scale: 'ratio', - timeScale: false, - }), - getPossibleOperationForField: () => ({ scale: 'ratio' }), - } as unknown as GenericOperationDefinition, - terms: { - input: 'field', - getPossibleOperationForField: () => ({ scale: 'ordinal' }), - } as unknown as GenericOperationDefinition, - sum: { - input: 'field', - filterable: true, - getPossibleOperationForField: () => ({ scale: 'ratio' }), - } as unknown as GenericOperationDefinition, - last_value: { - input: 'field', - getPossibleOperationForField: ({ type }) => ({ + average: createOperationDefinitionMock('average', {}, { label: 'avg' }), + terms: createOperationDefinitionMock('terms', {}, { scale: 'ordinal' }), + sum: createOperationDefinitionMock('sum', { filterable: true }), + last_value: createOperationDefinitionMock('last_value', { + getPossibleOperationForField: jest.fn(({ type }) => ({ scale: type === 'string' ? 'ordinal' : 'ratio', - }), - } as GenericOperationDefinition, - max: { - input: 'field', - getPossibleOperationForField: () => ({ scale: 'ratio' }), - } as unknown as GenericOperationDefinition, - count: { - input: 'field', - filterable: true, - buildColumn: ({ field }: { field: IndexPatternField }, columnsParams: PartialColumnParams) => ({ - label: 'avg', - dataType: 'number', - operationType: 'count', - sourceField: field.name, isBucketed: false, - scale: 'ratio', - timeScale: false, - filter: getFilter(undefined, columnsParams), - }), - getPossibleOperationForField: () => ({ scale: 'ratio' }), - } as unknown as GenericOperationDefinition, - derivative: { - input: 'fullReference', - getPossibleOperationForField: () => ({ scale: 'ratio' }), - } as unknown as GenericOperationDefinition, - moving_average: { + dataType: type === 'string' ? type : 'number', + })), + }), + max: createOperationDefinitionMock('max'), + count: createOperationDefinitionMock('count', { + filterable: true, + canReduceTimeRange: true, + }), + derivative: createOperationDefinitionMock('derivative', { input: 'fullReference' }), + moving_average: createOperationDefinitionMock('moving_average', { input: 'fullReference', operationParams: [{ name: 'window', type: 'number', required: true }], - buildColumn: ( - { references }: { references: string[] }, - columnsParams: PartialColumnParams - ) => ({ + filterable: true, + getErrorMessage: jest.fn(() => ['mock error']), + buildColumn: ({ referenceIds }, columnsParams) => ({ label: 'moving_average', dataType: 'number', operationType: 'moving_average', isBucketed: false, scale: 'ratio', - timeScale: false, + timeScale: undefined, params: { window: 5 }, - references, + references: referenceIds, filter: getFilter(undefined, columnsParams), }), - getErrorMessage: () => ['mock error'], - getPossibleOperationForField: () => ({ scale: 'ratio' }), - filterable: true, - } as unknown as GenericOperationDefinition, - cumulative_sum: { - input: 'fullReference', - getPossibleOperationForField: () => ({ scale: 'ratio' }), - } as unknown as GenericOperationDefinition, + }), + cumulative_sum: createOperationDefinitionMock('cumulative_sum', { input: 'fullReference' }), }; describe('formula', () => { @@ -550,7 +507,7 @@ describe('formula', () => { operationType: 'average', scale: 'ratio', sourceField: 'bytes', - timeScale: false, + timeScale: undefined, }, }, }); @@ -835,6 +792,99 @@ describe('formula', () => { }) ); }); + + it('add the formula reducedTimeRange to supported operations', () => { + const reducedTimeRange = '1h'; + const mergedColumn = { ...currentColumn, reducedTimeRange }; + const mergedLayer = { ...layer, columns: { ...layer.columns, col1: mergedColumn } }; + const formula = 'moving_average(average(bytes), window=7) + count()'; + + const { layer: newLayer } = insertOrReplaceFormulaColumn( + 'col1', + { + ...mergedColumn, + params: { + ...mergedColumn.params, + formula, + }, + }, + mergedLayer, + { + indexPattern, + operations: operationDefinitionMap, + } + ); + + // average and math are not filterable in the mocks + expect(newLayer.columns).toEqual( + expect.objectContaining({ + col1: expect.objectContaining({ + label: formula, + reducedTimeRange, + }), + // Moving average column + col1X1: expect.not.objectContaining({ + reducedTimeRange, + }), + col1X2: expect.objectContaining({ + operationType: 'count', + reducedTimeRange, + }), + }) + ); + + expect(newLayer.columns).toEqual( + expect.objectContaining({ + col1X0: expect.not.objectContaining({ + reducedTimeRange, + }), + col1X3: expect.not.objectContaining({ + reducedTimeRange, + }), + }) + ); + }); + + it('skip formula reducedTimeRange assignment to supported operations with already defined value', () => { + const reducedTimeRange = '1h'; + const mergedColumn = { ...currentColumn, reducedTimeRange }; + const mergedLayer = { ...layer, columns: { ...layer.columns, col1: mergedColumn } }; + const formula = `moving_average(average(bytes), window=7) + count(reducedTimeRange='1s')`; + + const { layer: newLayer } = insertOrReplaceFormulaColumn( + 'col1', + { + ...mergedColumn, + params: { + ...mergedColumn.params, + formula, + }, + }, + mergedLayer, + { + indexPattern, + operations: operationDefinitionMap, + } + ); + + // average and math are not filterable in the mocks + expect(newLayer.columns).toEqual( + expect.objectContaining({ + col1: expect.objectContaining({ + label: formula, + reducedTimeRange, + }), + // Moving average column + col1X1: expect.not.objectContaining({ + reducedTimeRange, + }), + col1X2: expect.objectContaining({ + operationType: 'count', + reducedTimeRange: '1s', + }), + }) + ); + }); }); describe('getErrorMessage', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx index be5a3f30282f..2715ab96cef0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx @@ -54,6 +54,11 @@ export const formulaOperation: OperationDefinition = Extract< + GenericOperationDefinition, + { input: Input } +>; + +export function createOperationDefinitionMock( + operation: string, + { + input = 'field', + getErrorMessage, + buildColumn, + ...params + }: Partial = {}, + { + label = operation, + dataType = 'number', + isBucketed = false, + scale = 'ratio', + timeScale, + }: Partial = {} +): OperationByInputType { + const sharedColumnParams = { + label, + dataType, + operationType: operation, + isBucketed, + scale, + timeScale, + }; + const sharedDefinitionParams = { + input, + getDefaultLabel: jest.fn(), + isTransferable: jest.fn(), + displayName: label, + type: operation, + getErrorMessage: getErrorMessage ?? jest.fn(), + toExpression: jest.fn(), + }; + if (input === 'field') { + return { + buildColumn: + buildColumn ?? + jest.fn(({ field }, columnParams: PartialColumnParams) => ({ + sourceField: field.name, + filter: getFilter(undefined, columnParams), + reducedTimeRange: columnParams.reducedTimeRange, + ...sharedColumnParams, + })), + onFieldChange: jest.fn(), + toEsAggsFn: jest.fn(), + getPossibleOperationForField: + (params as OperationByInputType).getPossibleOperationForField ?? + jest.fn((arg) => ({ + scale, + dataType, + isBucketed, + })), + ...sharedDefinitionParams, + ...params, + } as OperationByInputType; + } + if (input === 'fullReference') { + return { + buildColumn: + buildColumn ?? + jest.fn(({ referenceIds }, columnParams: PartialColumnParams) => ({ + references: referenceIds, + filter: getFilter(undefined, columnParams), + ...sharedColumnParams, + })), + onFieldChange: jest.fn(), + toEsAggsFn: jest.fn(), + getPossibleOperation: jest.fn(() => ({ + scale: 'ratio', + dataType: 'number', + isBucketed: false, + })), + requiredReferences: [ + { + input: ['field', 'managedReference'], + validateMetadata: jest.fn(), + }, + ], + selectionStyle: 'field', + ...sharedDefinitionParams, + ...params, + } as OperationByInputType; + } + return { + buildColumn: + buildColumn ?? + jest.fn((_, columnsParams: PartialColumnParams) => ({ + references: [], + filter: getFilter(undefined, columnsParams), + ...sharedColumnParams, + })), + getPossibleOperation: jest.fn(), + createCopy: jest.fn(), + ...sharedDefinitionParams, + ...params, + } as OperationByInputType; +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts index 5c9a27707093..10789e181dcb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts @@ -24,7 +24,7 @@ import { findVariables, getOperationParams, groupArgsByType, - mergeWithGlobalFilter, + mergeWithGlobalFilters, } from './util'; import { FormulaIndexPatternColumn, isFormulaIndexPatternColumn } from './formula'; import { getColumnOrder } from '../../layer_helpers'; @@ -77,7 +77,8 @@ function extractColumns( label: string ): Array<{ column: GenericIndexPatternColumn; location?: TinymathLocation }> { const columns: Array<{ column: GenericIndexPatternColumn; location?: TinymathLocation }> = []; - const globalFilter = layer.columns[idPrefix].filter; + const { filter: globalFilter, reducedTimeRange: globalReducedTimeRange } = + layer.columns[idPrefix]; function parseNode(node: TinymathAST) { if (typeof node === 'number' || node.type !== 'function') { @@ -110,10 +111,11 @@ function extractColumns( } const mappedParams = { - ...mergeWithGlobalFilter( + ...mergeWithGlobalFilters( nodeOperation, getOperationParams(nodeOperation, namedArguments || []), - globalFilter + globalFilter, + globalReducedTimeRange ), usedInMath: true, }; @@ -155,10 +157,11 @@ function extractColumns( mathColumn.label = label; } - const mappedParams = mergeWithGlobalFilter( + const mappedParams = mergeWithGlobalFilters( nodeOperation, getOperationParams(nodeOperation, namedArguments || []), - globalFilter + globalFilter, + globalReducedTimeRange ); const newCol = ( nodeOperation as OperationDefinition diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts index 29293d598d52..d66bac4cc871 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts @@ -49,12 +49,13 @@ export function getValueOrName(node: TinymathAST) { return node.name; } -export function mergeWithGlobalFilter( +export function mergeWithGlobalFilters( operation: | OperationDefinition | OperationDefinition, mappedParams: Record, - globalFilter?: Query + globalFilter?: Query, + globalReducedTimeRange?: string ) { if (globalFilter && operation.filterable) { const languageKey = 'kql' in mappedParams ? 'kql' : 'lucene'; @@ -68,6 +69,10 @@ export function mergeWithGlobalFilter( mappedParams[language] = globalFilter.query as string; } } + // Local definition override the global one + if (globalReducedTimeRange && operation.canReduceTimeRange && !mappedParams.reducedTimeRange) { + mappedParams.reducedTimeRange = globalReducedTimeRange; + } return mappedParams; } @@ -112,7 +117,6 @@ function getTypeI18n(type: string) { return ''; } -// Todo: i18n everything here export const tinymathFunctions: Record< string, { @@ -527,6 +531,26 @@ Example: Find the minimum between two fields averages `, }), }, + defaults: { + positionalArguments: [ + { + name: i18n.translate('xpack.lens.formula.value', { defaultMessage: 'value' }), + type: getTypeI18n('number'), + }, + { + name: i18n.translate('xpack.lens.formula.defaultValue', { defaultMessage: 'default' }), + type: getTypeI18n('number'), + }, + ], + help: i18n.translate('xpack.lens.formula.defaultFunction.markdown', { + defaultMessage: ` +Returns a default numeric value when value is null. + +Example: Return -1 when a field has no data +\`defaults(average(bytes), -1)\` + `, + }), + }, }; export function isMathNode(node: TinymathAST | string) { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 6c79d314d10f..bde67ea14a48 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -351,7 +351,7 @@ interface BaseOperationDefinitionProps< /** * Time range reducable operations can have a reduced time range defined at the dimension level - under the hood this will be translated into a filter on the defined time field */ - canReduceTimeRange?: boolean; + canReduceTimeRange?: boolean | { helpMessage: string }; shiftable?: boolean; getHelpMessage?: (props: HelpProps) => React.ReactNode; @@ -457,7 +457,7 @@ interface BaseBuildColumnArgs { indexPattern: IndexPattern; } -interface OperationParam { +export interface OperationParam { name: string; type: string; required?: boolean; @@ -667,7 +667,8 @@ interface ManagedReferenceOperationDefinition arg: BaseBuildColumnArgs & { previousColumn?: GenericIndexPatternColumn; }, - columnParams?: (ReferenceBasedIndexPatternColumn & C)['params'] & FilterParams, + columnParams?: (ReferenceBasedIndexPatternColumn & C)['params'] & + FilterParams & { reducedTimeRange?: string }, operationDefinitionMap?: Record ) => ReferenceBasedIndexPatternColumn & C; /** diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index b3f1b3bc7d5f..eb0f39262874 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -43,7 +43,7 @@ import { FormulaIndexPatternColumn, insertOrReplaceFormulaColumn } from './defin import type { TimeScaleUnit } from '../../../common/expressions'; import { documentField } from '../document_field'; import { isColumnOfType } from './definitions/helpers'; -import { DataType } from '../..'; +import type { DataType } from '../..'; export interface ColumnAdvancedParams { filter?: Query | undefined; @@ -530,6 +530,81 @@ export function insertNewColumn({ ); } +function replaceFormulaColumn( + { + operationDefinition, + layer, + previousColumn, + indexPattern, + previousDefinition, + columnId, + }: { + operationDefinition: Extract; + previousDefinition: GenericOperationDefinition; + layer: IndexPatternLayer; + previousColumn: IndexPatternLayer['columns'][number]; + indexPattern: IndexPattern; + columnId: string; + }, + { shouldResetLabel }: { shouldResetLabel?: boolean } +) { + const baseOptions = { + columns: layer.columns, + previousColumn, + indexPattern, + }; + let tempLayer = layer; + const newColumn = operationDefinition.buildColumn( + { ...baseOptions, layer: tempLayer }, + 'params' in previousColumn ? previousColumn.params : undefined, + operationDefinitionMap + ) as FormulaIndexPatternColumn; + + // now remove the previous references + if (previousDefinition.input === 'fullReference') { + (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => { + tempLayer = deleteColumn({ layer: tempLayer, columnId: id, indexPattern }); + }); + } + + const basicLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } }; + // rebuild the references again for the specific AST generated + let newLayer; + + try { + newLayer = newColumn.params.formula + ? insertOrReplaceFormulaColumn(columnId, newColumn, basicLayer, { + indexPattern, + }).layer + : basicLayer; + } catch (e) { + newLayer = basicLayer; + } + + // when coming to Formula keep the custom label + const regeneratedColumn = newLayer.columns[columnId]; + if ( + !shouldResetLabel && + regeneratedColumn.operationType !== previousColumn.operationType && + previousColumn.customLabel + ) { + regeneratedColumn.customLabel = true; + regeneratedColumn.label = previousColumn.label; + } + + return updateDefaultLabels( + adjustColumnReferencesForChangedColumn( + { + ...tempLayer, + columnOrder: getColumnOrder(newLayer), + columns: newLayer.columns, + }, + columnId + ), + indexPattern + ); +} + export function replaceColumn({ layer, columnId, @@ -678,54 +753,16 @@ export function replaceColumn({ // TODO: Refactor all this to be more generic and know less about Formula // if managed it has to look at the full picture to have a seamless transition if (operationDefinition.input === 'managedReference') { - const newColumn = operationDefinition.buildColumn( - { ...baseOptions, layer: tempLayer }, - 'params' in previousColumn ? previousColumn.params : undefined, - operationDefinitionMap - ) as FormulaIndexPatternColumn; - - // now remove the previous references - if (previousDefinition.input === 'fullReference') { - (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => { - tempLayer = deleteColumn({ layer: tempLayer, columnId: id, indexPattern }); - }); - } - - const basicLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } }; - // rebuild the references again for the specific AST generated - let newLayer; - - try { - newLayer = newColumn.params.formula - ? insertOrReplaceFormulaColumn(columnId, newColumn, basicLayer, { - indexPattern, - }).layer - : basicLayer; - } catch (e) { - newLayer = basicLayer; - } - - // when coming to Formula keep the custom label - const regeneratedColumn = newLayer.columns[columnId]; - if ( - !shouldResetLabel && - regeneratedColumn.operationType !== previousColumn.operationType && - previousColumn.customLabel - ) { - regeneratedColumn.customLabel = true; - regeneratedColumn.label = previousColumn.label; - } - - return updateDefaultLabels( - adjustColumnReferencesForChangedColumn( - { - ...tempLayer, - columnOrder: getColumnOrder(newLayer), - columns: newLayer.columns, - }, - columnId - ), - indexPattern + return replaceFormulaColumn( + { + operationDefinition, + layer: tempLayer, + previousColumn, + indexPattern, + previousDefinition, + columnId, + }, + { shouldResetLabel } ); } @@ -838,6 +875,20 @@ export function replaceColumn({ }, columnId ); + } else if (operationDefinition.input === 'managedReference') { + // Just changing a param in a formula column should trigger + // a full formula regeneration for side effects on referenced columns + return replaceFormulaColumn( + { + operationDefinition, + layer, + previousColumn, + indexPattern, + previousDefinition, + columnId, + }, + { shouldResetLabel } + ); } else { throw new Error('nothing changed'); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx index 77a359729a5e..612719967161 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx @@ -44,7 +44,7 @@ import { hasField } from './pure_utils'; import { mergeLayer } from './state_helpers'; import { supportsRarityRanking } from './operations/definitions/terms'; import { DEFAULT_MAX_DOC_COUNT } from './operations/definitions/terms/constants'; -import { getOriginalId } from '../../common/expressions'; +import { getOriginalId } from '../../common/expressions/datatable/transpose_helpers'; import { isQueryValid } from '../shared_components'; export function isColumnInvalid( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/visualize_geo_field_button.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/visualize_geo_field_button.tsx deleted file mode 100644 index 308e66dc6bd3..000000000000 --- a/x-pack/plugins/lens/public/indexpattern_datasource/visualize_geo_field_button.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { MouseEvent, useEffect, useState } from 'react'; -import { EuiButton } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - visualizeGeoFieldTrigger, - VISUALIZE_GEO_FIELD_TRIGGER, - UiActionsStart, -} from '@kbn/ui-actions-plugin/public'; -import { APP_ID } from '../../common/constants'; -import { IndexPattern } from '../types'; - -interface Props { - indexPattern: IndexPattern; - fieldName: string; - uiActions: UiActionsStart; -} - -export function VisualizeGeoFieldButton(props: Props) { - const [href, setHref] = useState(undefined); - - async function loadHref() { - const actions = await props.uiActions.getTriggerCompatibleActions(VISUALIZE_GEO_FIELD_TRIGGER, { - dataViewSpec: props.indexPattern.spec, - fieldName: props.fieldName, - }); - const triggerOptions = { - dataViewSpec: props.indexPattern.spec, - fieldName: props.fieldName, - trigger: visualizeGeoFieldTrigger, - }; - const loadedHref = actions.length ? await actions[0].getHref?.(triggerOptions) : undefined; - setHref(loadedHref); - } - - useEffect( - () => { - loadHref(); - }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - [] - ); - - function onClick(event: MouseEvent) { - event.preventDefault(); - props.uiActions.getTrigger(VISUALIZE_GEO_FIELD_TRIGGER).exec({ - dataViewSpec: props.indexPattern.spec, - fieldName: props.fieldName, - originatingApp: APP_ID, - }); - } - - return ( - <> - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - - - - ); -} diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.ts b/x-pack/plugins/lens/public/mocks/datasource_mock.ts index 65d001a726b8..7d66b705ae0f 100644 --- a/x-pack/plugins/lens/public/mocks/datasource_mock.ts +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.ts @@ -62,7 +62,7 @@ export function createMockDatasource(id: string): DatasourceMock { isTimeBased: jest.fn(), isValidColumn: jest.fn(), isEqual: jest.fn(), - getUsedDataView: jest.fn(), + getUsedDataView: jest.fn((state, layer) => 'mockip'), getUsedDataViews: jest.fn(), onRefreshIndexPattern: jest.fn(), }; diff --git a/x-pack/plugins/lens/public/mocks/visualization_mock.ts b/x-pack/plugins/lens/public/mocks/visualization_mock.ts index 23700094dc7c..27a6826e3d08 100644 --- a/x-pack/plugins/lens/public/mocks/visualization_mock.ts +++ b/x-pack/plugins/lens/public/mocks/visualization_mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { layerTypes } from '../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { Visualization, VisualizationMap } from '../types'; export function createMockVisualization(id = 'testVis'): jest.Mocked { @@ -14,8 +14,8 @@ export function createMockVisualization(id = 'testVis'): jest.Mocked state), removeLayer: jest.fn(), getLayerIds: jest.fn((_state) => ['layer1']), - getSupportedLayers: jest.fn(() => [{ type: layerTypes.DATA, label: 'Data Layer' }]), - getLayerType: jest.fn((_state, _layerId) => layerTypes.DATA), + getSupportedLayers: jest.fn(() => [{ type: LayerTypes.DATA, label: 'Data Layer' }]), + getLayerType: jest.fn((_state, _layerId) => LayerTypes.DATA), visualizationTypes: [ { icon: 'empty', diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 6019f566e0ba..6b06978befea 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -32,8 +32,8 @@ import type { ChartsPluginSetup, ChartsPluginStart } from '@kbn/charts-plugin/pu import type { EventAnnotationPluginSetup } from '@kbn/event-annotation-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public'; -import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; -import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; +import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public'; import { AppNavLinkStatus } from '@kbn/core/public'; import { @@ -48,7 +48,7 @@ import { import { createStartServicesGetter } from '@kbn/kibana-utils-plugin/public'; import type { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { AdvancedUiActionsSetup } from '@kbn/ui-actions-enhanced-plugin/public'; +import type { AdvancedUiActionsSetup } from '@kbn/ui-actions-enhanced-plugin/public'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import type { @@ -334,7 +334,8 @@ export class LensPlugin { async () => { const { getTimeZone } = await import('./utils'); return getTimeZone(core.uiSettings); - } + }, + () => startServices().plugins.data.nowProvider.get() ); const getPresentationUtilContext = () => diff --git a/x-pack/plugins/lens/public/shared_components/axis_extent/types.ts b/x-pack/plugins/lens/public/shared_components/axis_extent/types.ts index 77b91be17360..aa67f0a01e99 100644 --- a/x-pack/plugins/lens/public/shared_components/axis_extent/types.ts +++ b/x-pack/plugins/lens/public/shared_components/axis_extent/types.ts @@ -5,6 +5,6 @@ * 2.0. */ -import { AxisExtentConfig } from '@kbn/expression-xy-plugin/common'; +import type { AxisExtentConfig } from '@kbn/expression-xy-plugin/common'; export type UnifiedAxisExtentConfig = AxisExtentConfig; diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts index f83786238f62..559120139894 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts @@ -21,7 +21,7 @@ import { selectTriggerApplyChanges, selectChangesApplied, } from '.'; -import { layerTypes } from '../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { makeLensStore, defaultState, mockStoreDeps } from '../mocks'; import { DatasourceMap, VisualizationMap } from '../types'; import { applyChanges, disableAutoApply, enableAutoApply, setChangesApplied } from './lens_slice'; @@ -306,7 +306,7 @@ describe('lensSlice', () => { (layerIds as string[]).filter((id: string) => id !== layerId), getLayerIds: (layerIds: unknown) => layerIds as string[], appendLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], - getSupportedLayers: jest.fn(() => [{ type: layerTypes.DATA, label: 'Data Layer' }]), + getSupportedLayers: jest.fn(() => [{ type: LayerTypes.DATA, label: 'Data Layer' }]), }, }; @@ -339,7 +339,7 @@ describe('lensSlice', () => { customStore.dispatch( addLayer({ layerId: 'foo', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }) ); const state = customStore.getState().lens; diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.test.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.test.ts index 92a3ef01fdb7..ae7e84c61b3c 100644 --- a/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.test.ts +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.test.ts @@ -118,6 +118,15 @@ describe('IndexPattern Data Source', () => { query: { sql: 'SELECT * FROM foo' }, }, }, + fieldList: [ + { + id: 'col1', + name: 'Test 1', + meta: { + type: 'number', + }, + }, + ], } as unknown as TextBasedLanguagesPrivateState; }); @@ -159,7 +168,9 @@ describe('IndexPattern Data Source', () => { describe('#getPersistedState', () => { it('should persist from saved state', async () => { expect(textBasedLanguagesDatasource.getPersistableState(baseState)).toEqual({ - state: baseState, + state: { + layers: baseState.layers, + }, savedObjectReferences: [ { name: 'textBasedLanguages-datasource-layer-a', type: 'index-pattern', id: 'foo' }, ], @@ -649,6 +660,22 @@ describe('IndexPattern Data Source', () => { index: 'foo', }, }, + fieldList: [ + { + id: 'col1', + name: 'Test 1', + meta: { + type: 'number', + }, + }, + { + id: 'col2', + name: 'Test 2', + meta: { + type: 'number', + }, + }, + ], } as unknown as TextBasedLanguagesPrivateState; publicAPI = textBasedLanguagesDatasource.getPublicAPI({ @@ -661,6 +688,28 @@ describe('IndexPattern Data Source', () => { { columnId: 'col2', fields: ['Test 2'] }, ]); }); + + it('should return only the columns that exist on the query', () => { + const state = { + ...baseState, + fieldList: [ + { + id: 'col2', + name: 'Test 2', + meta: { + type: 'number', + }, + }, + ], + } as unknown as TextBasedLanguagesPrivateState; + + publicAPI = textBasedLanguagesDatasource.getPublicAPI({ + state, + layerId: 'a', + indexPatterns, + }); + expect(publicAPI.getTableSpec()).toEqual([]); + }); }); describe('getOperationForColumnId', () => { diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.tsx b/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.tsx index e9ab24b8392c..7157f643c6e8 100644 --- a/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.tsx +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/text_based_languages.tsx @@ -360,7 +360,7 @@ export function getTextBasedLanguagesDatasource({ customLabel = selectedField?.fieldName; } - const columnExists = props.state.fieldList.some((f) => f.name === selectedField?.fieldName); + const columnExists = props.state.fieldList.some((f) => f.name === customLabel); render( { + const columns = state.layers[layerId]?.columns.filter((c) => { + const columnExists = state?.fieldList?.some((f) => f.name === c?.fieldName); + if (columnExists) return c; + }); return ( - state.layers[layerId]?.columns?.map((column) => ({ + columns.map((column) => ({ columnId: column.columnId, fields: [column.fieldName], })) || [] diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/utils.test.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/utils.test.ts index bc511bb2b86c..1e7096414d19 100644 --- a/x-pack/plugins/lens/public/text_based_languages_datasource/utils.test.ts +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/utils.test.ts @@ -5,7 +5,7 @@ * 2.0. */ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; - +import type { DatatableColumn } from '@kbn/expressions-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; @@ -14,7 +14,9 @@ import { getIndexPatternFromTextBasedQuery, loadIndexPatternRefs, getStateFromAggregateQuery, + getAllColumns, } from './utils'; +import type { TextBasedLanguagesLayerColumn } from './types'; import { type AggregateQuery } from '@kbn/es-query'; jest.mock('./fetch_data_from_aggregate_query', () => ({ @@ -73,6 +75,74 @@ describe('Text based languages utils', () => { }); }); + describe('getAllColumns', () => { + it('should remove columns that do not exist on the query and remove duplicates', async () => { + const existingOnLayer = [ + { + fieldName: 'time', + columnId: 'time', + meta: { + type: 'date', + }, + }, + { + fieldName: 'bytes', + columnId: 'bytes', + meta: { + type: 'number', + }, + }, + ] as TextBasedLanguagesLayerColumn[]; + const columnsFromQuery = [ + { + name: 'timestamp', + id: 'timestamp', + meta: { + type: 'date', + }, + }, + { + name: 'bytes', + id: 'bytes', + meta: { + type: 'number', + }, + }, + { + name: 'memory', + id: 'memory', + meta: { + type: 'number', + }, + }, + ] as DatatableColumn[]; + const allColumns = getAllColumns(existingOnLayer, columnsFromQuery); + expect(allColumns).toStrictEqual([ + { + fieldName: 'bytes', + columnId: 'bytes', + meta: { + type: 'number', + }, + }, + { + fieldName: 'timestamp', + columnId: 'timestamp', + meta: { + type: 'date', + }, + }, + { + fieldName: 'memory', + columnId: 'memory', + meta: { + type: 'number', + }, + }, + ]); + }); + }); + describe('getStateFromAggregateQuery', () => { it('should return the correct state', async () => { const state = { diff --git a/x-pack/plugins/lens/public/text_based_languages_datasource/utils.ts b/x-pack/plugins/lens/public/text_based_languages_datasource/utils.ts index c4e41103f0fd..5504cd39bd6a 100644 --- a/x-pack/plugins/lens/public/text_based_languages_datasource/utils.ts +++ b/x-pack/plugins/lens/public/text_based_languages_datasource/utils.ts @@ -35,6 +35,34 @@ export async function loadIndexPatternRefs( }); } +export const getAllColumns = ( + existingColumns: TextBasedLanguagesLayerColumn[], + columnsFromQuery: DatatableColumn[] +) => { + // filter out columns that do not exist on the query + const columns = existingColumns.filter((c) => { + const columnExists = columnsFromQuery?.some((f) => f.name === c?.fieldName); + if (columnExists) return c; + }); + const allCols = [ + ...columns, + ...columnsFromQuery.map((c) => ({ columnId: c.id, fieldName: c.id, meta: c.meta })), + ]; + const uniqueIds: string[] = []; + + return allCols.filter((col) => { + const isDuplicate = uniqueIds.includes(col.columnId); + + if (!isDuplicate) { + uniqueIds.push(col.columnId); + + return true; + } + + return false; + }); +}; + export async function getStateFromAggregateQuery( state: TextBasedLanguagesPrivateState, query: AggregateQuery, @@ -59,12 +87,7 @@ export async function getStateFromAggregateQuery( const dataView = await dataViews.get(index); timeFieldName = dataView.timeFieldName; columnsFromQuery = table?.columns ?? []; - const existingColumns = state.layers[newLayerId].allColumns; - - allColumns = [ - ...existingColumns, - ...columnsFromQuery.map((c) => ({ columnId: c.id, fieldName: c.id, meta: c.meta })), - ]; + allColumns = getAllColumns(state.layers[newLayerId].allColumns, columnsFromQuery); } catch (e) { errors.push(e); } diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts index b04f7115d3d1..18782d618a24 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts @@ -8,9 +8,8 @@ import { i18n } from '@kbn/i18n'; import { createAction } from '@kbn/ui-actions-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; -import { IEmbeddable } from '@kbn/embeddable-plugin/public'; -import { DataViewsService } from '@kbn/data-views-plugin/public'; -import { execute, isCompatible, getHref } from './open_in_discover_helpers'; +import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import type { DataViewsService } from '@kbn/data-views-plugin/public'; const ACTION_OPEN_IN_DISCOVER = 'ACTION_OPEN_IN_DISCOVER'; @@ -18,6 +17,8 @@ interface Context { embeddable: IEmbeddable; } +export const getDiscoverHelpersAsync = async () => await import('./open_in_discover_helpers'); + export const createOpenInDiscoverAction = ( discover: Pick, dataViews: Pick, @@ -33,6 +34,7 @@ export const createOpenInDiscoverAction = ( defaultMessage: 'Explore data in Discover', }), getHref: async (context: Context) => { + const { getHref } = await getDiscoverHelpersAsync(); return getHref({ discover, dataViews, @@ -41,6 +43,7 @@ export const createOpenInDiscoverAction = ( }); }, isCompatible: async (context: Context) => { + const { isCompatible } = await getDiscoverHelpersAsync(); return isCompatible({ hasDiscoverAccess, discover, @@ -49,6 +52,7 @@ export const createOpenInDiscoverAction = ( }); }, execute: async (context: Context) => { + const { execute } = await getDiscoverHelpersAsync(); return execute({ ...context, discover, dataViews, hasDiscoverAccess }); }, }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx index 26332f0cf005..84ae2a21a9a9 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx @@ -48,14 +48,16 @@ describe('open in discover drilldown', () => { instance.find('EuiSwitch').prop('onChange')!({} as unknown as FormEvent<{}>); expect(setConfig).toHaveBeenCalledWith({ openInNewTab: true }); }); - it('calls through to isCompatible helper', () => { + + it('calls through to isCompatible helper', async () => { const filters: Filter[] = [{ meta: { disabled: false } }]; - drilldown.isCompatible( + await drilldown.isCompatible( { openInNewTab: true }, { embeddable: { type: 'lens' } as IEmbeddable, filters } ); expect(isCompatible).toHaveBeenCalledWith(expect.objectContaining({ filters })); }); + it('calls through to getHref helper', async () => { const filters: Filter[] = [{ meta: { disabled: false } }]; await drilldown.execute( diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx index 5518f0f99ff5..fa262c6b0d53 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -6,22 +6,23 @@ */ import React from 'react'; -import { IEmbeddable, EmbeddableInput } from '@kbn/embeddable-plugin/public'; +import type { IEmbeddable, EmbeddableInput } from '@kbn/embeddable-plugin/public'; import type { Query, Filter, TimeRange } from '@kbn/es-query'; import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; import type { ApplicationStart } from '@kbn/core/public'; -import { CollectConfigProps as CollectConfigPropsBase } from '@kbn/kibana-utils-plugin/public'; +import type { SerializableRecord } from '@kbn/utility-types'; +import type { CollectConfigProps as CollectConfigPropsBase } from '@kbn/kibana-utils-plugin/public'; import { reactToUiComponent } from '@kbn/kibana-react-plugin/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown, UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext, } from '@kbn/ui-actions-enhanced-plugin/public'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; -import { DiscoverSetup } from '@kbn/discover-plugin/public'; -import { ApplyGlobalFilterActionContext } from '@kbn/unified-search-plugin/public'; +import type { DiscoverSetup } from '@kbn/discover-plugin/public'; +import type { ApplyGlobalFilterActionContext } from '@kbn/unified-search-plugin/public'; import { i18n } from '@kbn/i18n'; -import { DataViewsService } from '@kbn/data-views-plugin/public'; -import { isCompatible, isLensEmbeddable, getHref, getLocation } from './open_in_discover_helpers'; +import type { DataViewsService } from '@kbn/data-views-plugin/public'; +import { DOC_TYPE } from '../../common/constants'; interface EmbeddableQueryInput extends EmbeddableInput { query?: Query; @@ -29,6 +30,8 @@ interface EmbeddableQueryInput extends EmbeddableInput { timeRange?: TimeRange; } +export const getDiscoverHelpersAsync = async () => await import('./open_in_discover_helpers'); + /** @internal */ export type EmbeddableWithQueryInput = IEmbeddable; @@ -41,10 +44,9 @@ interface UrlDrilldownDeps { export type ActionContext = ApplyGlobalFilterActionContext; -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type Config = { +export interface Config extends SerializableRecord { openInNewTab: boolean; -}; +} export type OpenInDiscoverTrigger = typeof APPLY_FILTER_TRIGGER; @@ -53,12 +55,10 @@ export interface ActionFactoryContext extends BaseActionFactoryContext { } export type CollectConfigProps = CollectConfigPropsBase; -const OPEN_IN_DISCOVER_DRILLDOWN = 'OPEN_IN_DISCOVER_DRILLDOWN'; - export class OpenInDiscoverDrilldown implements Drilldown { - public readonly id = OPEN_IN_DISCOVER_DRILLDOWN; + public readonly id = 'OPEN_IN_DISCOVER_DRILLDOWN'; constructor(private readonly deps: UrlDrilldownDeps) {} @@ -75,11 +75,7 @@ export class OpenInDiscoverDrilldown return [APPLY_FILTER_TRIGGER]; } - private readonly ReactCollectConfig: React.FC = ({ - config, - onConfig, - context, - }) => { + private readonly ReactCollectConfig: React.FC = ({ config, onConfig }) => { return ( { + const { isCompatible } = await getDiscoverHelpersAsync(); + return isCompatible({ discover: this.deps.discover, dataViews: this.deps.dataViews(), @@ -117,11 +115,12 @@ export class OpenInDiscoverDrilldown }); }; - public readonly isConfigurable = (context: ActionFactoryContext) => { - return this.deps.hasDiscoverAccess() && isLensEmbeddable(context.embeddable as IEmbeddable); - }; + public readonly isConfigurable = (context: ActionFactoryContext) => + this.deps.hasDiscoverAccess() && context.embeddable?.type === DOC_TYPE; public readonly getHref = async (config: Config, context: ActionContext) => { + const { getHref } = await getDiscoverHelpersAsync(); + return getHref({ discover: this.deps.discover, dataViews: this.deps.dataViews(), @@ -135,6 +134,8 @@ export class OpenInDiscoverDrilldown if (config.openInNewTab) { window.open(await this.getHref(config, context), '_blank'); } else { + const { getLocation } = await getDiscoverHelpersAsync(); + const { app, path, state } = await getLocation({ discover: this.deps.discover, dataViews: this.deps.dataViews(), diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index c40403cfd092..5b55dfe2054a 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -26,8 +26,8 @@ import { isOperation, } from './types'; import type { DatasourceStates, VisualizationState } from './state_management'; -import { IndexPatternServiceAPI } from './data_views_service/service'; -import { DraggingIdentifier } from './drag_drop'; +import type { IndexPatternServiceAPI } from './data_views_service/service'; +import type { DraggingIdentifier } from './drag_drop'; export function getVisualizeGeoFieldMessage(fieldType: string) { return i18n.translate('xpack.lens.visualizeGeoFieldMessage', { diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.tsx index baad0419074d..3f0d377c7c97 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.tsx @@ -10,7 +10,7 @@ import { EuiDataGridCellValueElementProps, EuiLink } from '@elastic/eui'; import type { IUiSettingsClient } from '@kbn/core/public'; import classNames from 'classnames'; import type { FormatFactory } from '../../../../common'; -import { getOriginalId } from '../../../../common/expressions'; +import { getOriginalId } from '../../../../common/expressions/datatable/transpose_helpers'; import type { ColumnConfig } from '../../../../common/expressions'; import type { DataContextType } from './types'; import { getContrastColor, getNumericValue } from '../../../shared_components/coloring/utils'; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx index e3b2675e2bd0..a1c6dc550d7a 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx @@ -21,7 +21,7 @@ import { TableDimensionEditor } from './dimension_editor'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { act } from 'react-dom/test-utils'; import { PalettePanelContainer } from '../../../shared_components'; -import { layerTypes } from '../../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; describe('data table dimension editor', () => { let frame: FramePublicAPI; @@ -34,7 +34,7 @@ describe('data table dimension editor', () => { function testState(): DatatableVisualizationState { return { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [ { columnId: 'foo', diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx index 091cd4faadf3..ed0048ca0d40 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx @@ -18,8 +18,8 @@ import { EuiButtonEmpty, } from '@elastic/eui'; import { CustomizablePalette, PaletteRegistry, FIXED_PROGRESSION } from '@kbn/coloring'; -import { VisualizationDimensionEditorProps } from '../../../types'; -import { DatatableVisualizationState } from '../visualization'; +import type { VisualizationDimensionEditorProps } from '../../../types'; +import type { DatatableVisualizationState } from '../visualization'; import { applyPaletteParams, @@ -27,7 +27,8 @@ import { PalettePanelContainer, findMinMaxByColumnId, } from '../../../shared_components'; -import { isNumericFieldForDatatable, getOriginalId } from '../../../../common/expressions'; +import { isNumericFieldForDatatable } from '../../../../common/expressions/datatable/utils'; +import { getOriginalId } from '../../../../common/expressions/datatable/transpose_helpers'; import './dimension_editor.scss'; import { CollapseSetting } from '../../../shared_components/collapse_setting'; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor_additional_section.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor_additional_section.test.tsx index 859c3867b0ce..3262a786c72f 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor_additional_section.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor_additional_section.test.tsx @@ -14,7 +14,7 @@ import { createMockDatasource, createMockFramePublicAPI } from '../../../mocks'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { TableDimensionEditorAdditionalSection } from './dimension_editor_addtional_section'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; -import { layerTypes } from '../../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; describe('data table dimension editor additional section', () => { let frame: FramePublicAPI; @@ -27,7 +27,7 @@ describe('data table dimension editor additional section', () => { function testState(): DatatableVisualizationState { return { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [ { columnId: 'foo', diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor_addtional_section.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor_addtional_section.tsx index c61a512991b6..19bcfcbabd53 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor_addtional_section.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor_addtional_section.tsx @@ -10,17 +10,19 @@ import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import { EuiFormRow, EuiFieldText, EuiText, useEuiTheme, EuiComboBox } from '@elastic/eui'; import { PaletteRegistry } from '@kbn/coloring'; -import { VisualizationDimensionEditorProps } from '../../../types'; -import { DatatableVisualizationState } from '../visualization'; +import type { VisualizationDimensionEditorProps } from '../../../types'; +import type { DatatableVisualizationState } from '../visualization'; import { useDebouncedValue } from '../../../shared_components'; import type { ColumnState } from '../../../../common/expressions'; + import { - isNumericFieldForDatatable, getDefaultSummaryLabel, getFinalSummaryConfiguration, getSummaryRowOptions, -} from '../../../../common/expressions'; +} from '../../../../common/expressions/datatable/summary'; + +import { isNumericFieldForDatatable } from '../../../../common/expressions/datatable/utils'; import './dimension_editor.scss'; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.test.ts b/x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.test.ts index 760145e06c33..2a517acc3308 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.test.ts @@ -17,7 +17,7 @@ import { createGridHideHandler, createTransposeColumnFilterHandler, } from './table_actions'; -import { LensGridDirection, ColumnConfig } from '../../../../common/expressions'; +import type { LensGridDirection, ColumnConfig } from '../../../../common/expressions'; function getDefaultConfig(): ColumnConfig { return { diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.ts b/x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.ts index b4075ef84898..07aea191a365 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.ts +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.ts @@ -10,7 +10,7 @@ import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common' import { ClickTriggerEvent } from '@kbn/charts-plugin/public'; import type { LensResizeAction, LensSortAction, LensToggleAction } from './types'; import type { ColumnConfig, LensGridDirection } from '../../../../common/expressions'; -import { getOriginalId } from '../../../../common/expressions'; +import { getOriginalId } from '../../../../common/expressions/datatable/transpose_helpers'; export const createGridResizeHandler = ( diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx index 4c45e139bcc2..3583225584ec 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx @@ -16,7 +16,7 @@ import { VisualizationContainer } from '../../../visualization_container'; import { EmptyPlaceholder } from '@kbn/charts-plugin/public'; import { IconChartDatatable } from '@kbn/chart-icons'; import { DataContext, DatatableComponent } from './table_basic'; -import { DatatableProps } from '../../../../common/expressions'; +import type { DatatableProps } from '../../../../common/expressions'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { IUiSettingsClient } from '@kbn/core/public'; import { Datatable, RenderMode } from '@kbn/expressions-plugin/common'; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx index c3ef4d1c1ea0..bbf5eddef879 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx @@ -52,7 +52,8 @@ import { createGridSortingConfig, createTransposeColumnFilterHandler, } from './table_actions'; -import { getOriginalId, getFinalSummaryConfiguration } from '../../../../common/expressions'; +import { getFinalSummaryConfiguration } from '../../../../common/expressions/datatable/summary'; +import { getOriginalId } from '../../../../common/expressions/datatable/transpose_helpers'; export const DataContext = React.createContext({}); diff --git a/x-pack/plugins/lens/public/visualizations/datatable/expression.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/expression.test.tsx index 7bd3bac4e950..eb3835f95b6a 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/expression.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/expression.test.tsx @@ -9,7 +9,7 @@ import type { DatatableProps } from '../../../common/expressions'; import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks'; import type { FormatFactory } from '../../../common'; import { getDatatable } from '../../../common/expressions'; -import { Datatable } from '@kbn/expressions-plugin/common'; +import type { Datatable } from '@kbn/expressions-plugin/common'; function sampleArgs() { const indexPatternId = 'indexPatternId'; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx b/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx index 93a41f9baf53..da7fbceee5b2 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx @@ -12,7 +12,7 @@ import { I18nProvider } from '@kbn/i18n-react'; import type { PaletteRegistry } from '@kbn/coloring'; import type { IAggType } from '@kbn/data-plugin/public'; import { IUiSettingsClient, ThemeServiceStart } from '@kbn/core/public'; -import { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; +import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { trackUiCounterEvents } from '../../lens_ui_telemetry'; import { DatatableComponent } from './components/table_basic'; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.test.tsx index 85d1bc3f1a57..cc4b108a23bd 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.test.tsx @@ -17,7 +17,7 @@ import { VisualizationDimensionGroupConfig, } from '../../types'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { themeServiceMock } from '@kbn/core/public/mocks'; function mockFrame(): FramePublicAPI { @@ -37,7 +37,7 @@ describe('Datatable Visualization', () => { it('should initialize from the empty state', () => { expect(datatableVisualization.initialize(() => 'aaa', undefined)).toEqual({ layerId: 'aaa', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [], }); }); @@ -45,7 +45,7 @@ describe('Datatable Visualization', () => { it('should initialize from a persisted state', () => { const expectedState: DatatableVisualizationState = { layerId: 'foo', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'saved' }], }; expect(datatableVisualization.initialize(() => 'foo', expectedState)).toEqual(expectedState); @@ -56,7 +56,7 @@ describe('Datatable Visualization', () => { it('return the layer ids', () => { const state: DatatableVisualizationState = { layerId: 'baz', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'a' }, { columnId: 'b' }, { columnId: 'c' }], }; expect(datatableVisualization.getLayerIds(state)).toEqual(['baz']); @@ -67,12 +67,12 @@ describe('Datatable Visualization', () => { it('should reset the layer', () => { const state: DatatableVisualizationState = { layerId: 'baz', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'a' }, { columnId: 'b' }, { columnId: 'c' }], }; expect(datatableVisualization.clearLayer(state, 'baz', 'indexPattern1')).toMatchObject({ layerId: 'baz', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [], }); }); @@ -88,10 +88,10 @@ describe('Datatable Visualization', () => { it('should return the type only if the layer is in the state', () => { const state: DatatableVisualizationState = { layerId: 'baz', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'a' }, { columnId: 'b' }, { columnId: 'c' }], }; - expect(datatableVisualization.getLayerType('baz', state)).toEqual(layerTypes.DATA); + expect(datatableVisualization.getLayerType('baz', state)).toEqual(LayerTypes.DATA); expect(datatableVisualization.getLayerType('foo', state)).toBeUndefined(); }); }); @@ -123,7 +123,7 @@ describe('Datatable Visualization', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'col1' }], }, table: { @@ -153,7 +153,7 @@ describe('Datatable Visualization', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'col1' }], }, table: { @@ -172,7 +172,7 @@ describe('Datatable Visualization', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [ { columnId: 'col1', width: 123 }, { columnId: 'col2', hidden: true }, @@ -207,7 +207,7 @@ describe('Datatable Visualization', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'col1' }], }, table: { @@ -226,7 +226,7 @@ describe('Datatable Visualization', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'col1' }], }, table: { @@ -245,7 +245,7 @@ describe('Datatable Visualization', () => { const suggestions = datatableVisualization.getSuggestions({ state: { layerId: 'older', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'col1' }], }, table: { @@ -286,7 +286,7 @@ describe('Datatable Visualization', () => { layerId: 'first', state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [], }, frame, @@ -302,7 +302,7 @@ describe('Datatable Visualization', () => { layerId: 'first', state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [], }, frame, @@ -342,7 +342,7 @@ describe('Datatable Visualization', () => { layerId: 'first', state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [], }, frame, @@ -380,7 +380,7 @@ describe('Datatable Visualization', () => { layerId: 'a', state: { layerId: 'a', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }], }, frame, @@ -395,7 +395,7 @@ describe('Datatable Visualization', () => { datatableVisualization.removeDimension({ prevState: { layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }], }, layerId: 'layer1', @@ -404,7 +404,7 @@ describe('Datatable Visualization', () => { }) ).toEqual({ layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'c' }], }); }); @@ -412,7 +412,7 @@ describe('Datatable Visualization', () => { it('should handle correctly the sorting state on removing dimension', () => { const state = { layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }], }; expect( @@ -425,7 +425,7 @@ describe('Datatable Visualization', () => { ).toEqual({ sorting: undefined, layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'c' }], }); @@ -439,7 +439,7 @@ describe('Datatable Visualization', () => { ).toEqual({ sorting: { columnId: 'c', direction: 'asc' }, layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'c' }], }); }); @@ -451,7 +451,7 @@ describe('Datatable Visualization', () => { datatableVisualization.setDimension({ prevState: { layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }], }, layerId: 'layer1', @@ -461,7 +461,7 @@ describe('Datatable Visualization', () => { }) ).toEqual({ layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }, { columnId: 'd', isTransposed: false }], }); }); @@ -471,7 +471,7 @@ describe('Datatable Visualization', () => { datatableVisualization.setDimension({ prevState: { layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }], }, layerId: 'layer1', @@ -481,7 +481,7 @@ describe('Datatable Visualization', () => { }) ).toEqual({ layerId: 'layer1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b', isTransposed: false }, { columnId: 'c' }], }); }); @@ -501,7 +501,7 @@ describe('Datatable Visualization', () => { const defaultExpressionTableState = { layerId: 'a', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }], }; @@ -716,7 +716,7 @@ describe('Datatable Visualization', () => { const error = datatableVisualization.getErrorMessages({ layerId: 'a', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }], }); @@ -741,7 +741,7 @@ describe('Datatable Visualization', () => { const error = datatableVisualization.getErrorMessages({ layerId: 'a', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'b' }, { columnId: 'c' }], }); @@ -753,7 +753,7 @@ describe('Datatable Visualization', () => { it('should add a sort column to the state', () => { const currentState: DatatableVisualizationState = { layerId: 'foo', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'saved' }], }; expect( @@ -773,7 +773,7 @@ describe('Datatable Visualization', () => { it('should add a custom width to a column in the state', () => { const currentState: DatatableVisualizationState = { layerId: 'foo', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'saved' }], }; expect( @@ -790,7 +790,7 @@ describe('Datatable Visualization', () => { it('should clear custom width value for the column from the state', () => { const currentState: DatatableVisualizationState = { layerId: 'foo', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'saved', width: 5000 }], }; expect( @@ -807,7 +807,7 @@ describe('Datatable Visualization', () => { it('should update page size', () => { const currentState: DatatableVisualizationState = { layerId: 'foo', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: [{ columnId: 'saved', width: 5000 }], paging: { enabled: true, size: 10 }, }; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx index ca6f6775f893..ad5e8118c522 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx @@ -15,6 +15,7 @@ import { ThemeServiceStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { IconChartDatatable } from '@kbn/chart-icons'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { SuggestionRequest, Visualization, @@ -24,11 +25,11 @@ import type { } from '../../types'; import { TableDimensionEditor } from './components/dimension_editor'; import { TableDimensionEditorAdditionalSection } from './components/dimension_editor_addtional_section'; -import { LayerType, layerTypes } from '../../../common'; -import { getDefaultSummaryLabel, PagingState } from '../../../common/expressions'; -import type { ColumnState, SortingState } from '../../../common/expressions'; +import type { LayerType } from '../../../common'; +import { getDefaultSummaryLabel } from '../../../common/expressions/datatable/summary'; +import type { ColumnState, SortingState, PagingState } from '../../../common/expressions'; import { DataTableToolbar } from './components/toolbar'; -import { IndexPatternLayer } from '../../indexpattern_datasource/types'; +import type { IndexPatternLayer } from '../../indexpattern_datasource/types'; export interface DatatableVisualizationState { columns: ColumnState[]; @@ -108,7 +109,7 @@ export const getDatatableVisualization = ({ state || { columns: [], layerId: addNewLayer(), - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } ); }, @@ -177,7 +178,7 @@ export const getDatatableVisualization = ({ state: { ...(state || {}), layerId: table.layerId, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, columns: table.columns.map((col, columnIndex) => ({ ...(oldColumnSettings[col.columnId] || {}), isTransposed: usesTransposing && columnIndex < lastTransposedColumnIndex, @@ -362,7 +363,7 @@ export const getDatatableVisualization = ({ getSupportedLayers() { return [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: i18n.translate('xpack.lens.datatable.addLayer', { defaultMessage: 'Visualization', }), diff --git a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx index 056b96619949..c886eaa360ec 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx @@ -25,14 +25,15 @@ import { } from '@kbn/coloring'; import { GaugeTicksPositions, GaugeColorModes } from '@kbn/expression-gauge-plugin/common'; import { getMaxValue, getMinValue } from '@kbn/expression-gauge-plugin/public'; -import { isNumericFieldForDatatable } from '../../../common/expressions'; +import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils'; import { applyPaletteParams, PalettePanelContainer, TooltipWrapper } from '../../shared_components'; import type { VisualizationDimensionEditorProps } from '../../types'; -import './dimension_editor.scss'; -import { GaugeVisualizationState } from './constants'; +import type { GaugeVisualizationState } from './constants'; import { defaultPaletteParams } from './palette_config'; import { getAccessorsFromState } from './utils'; +import './dimension_editor.scss'; + export function GaugeDimensionEditor( props: VisualizationDimensionEditorProps & { paletteService: PaletteRegistry; diff --git a/x-pack/plugins/lens/public/visualizations/gauge/suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/gauge/suggestions.test.ts index 0b974cd3de8f..5b3eb512426b 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/suggestions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/gauge/suggestions.test.ts @@ -6,7 +6,7 @@ */ import { getSuggestions } from './suggestions'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { GaugeShapes } from '@kbn/expression-gauge-plugin/common'; import { GaugeVisualizationState } from './constants'; @@ -43,7 +43,7 @@ describe('gauge suggestions', () => { state: { shape: GaugeShapes.HORIZONTAL_BULLET, layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as GaugeVisualizationState, keptLayerIds: ['first'], }; @@ -59,7 +59,7 @@ describe('gauge suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as GaugeVisualizationState, keptLayerIds: ['first'], }; @@ -77,7 +77,7 @@ describe('gauge suggestions', () => { state: { shape: GaugeShapes.HORIZONTAL_BULLET, layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, minAccessor: 'some-field', labelMajorMode: 'auto', ticksPosition: 'auto', @@ -97,7 +97,7 @@ describe('gauge suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as GaugeVisualizationState, keptLayerIds: ['first'], }) @@ -120,7 +120,7 @@ describe('gauge suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as GaugeVisualizationState, keptLayerIds: ['first'], }) @@ -141,7 +141,7 @@ describe('shows suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as GaugeVisualizationState, keptLayerIds: ['first'], }) @@ -149,7 +149,7 @@ describe('shows suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: GaugeShapes.HORIZONTAL_BULLET, metricAccessor: 'metric-column', labelMajorMode: 'auto', @@ -187,7 +187,7 @@ describe('shows suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: GaugeShapes.HORIZONTAL_BULLET, metricAccessor: 'metric-column', } as GaugeVisualizationState, @@ -197,7 +197,7 @@ describe('shows suggestions', () => { ).toEqual([ { state: { - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: GaugeShapes.VERTICAL_BULLET, metricAccessor: 'metric-column', labelMajorMode: 'auto', diff --git a/x-pack/plugins/lens/public/visualizations/gauge/suggestions.ts b/x-pack/plugins/lens/public/visualizations/gauge/suggestions.ts index 46688d3021fe..e90aa5e7d089 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/gauge/suggestions.ts @@ -12,9 +12,9 @@ import { GaugeTicksPositions, GaugeLabelMajorModes, } from '@kbn/expression-gauge-plugin/common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { TableSuggestion, Visualization } from '../../types'; -import { layerTypes } from '../../../common'; -import { GaugeVisualizationState } from './constants'; +import type { GaugeVisualizationState } from './constants'; const isNotNumericMetric = (table: TableSuggestion) => table.columns?.[0]?.operation.dataType !== 'number' || @@ -57,7 +57,7 @@ export const getSuggestions: Visualization['getSuggesti ...state, shape, layerId: table.layerId, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, ticksPosition: GaugeTicksPositions.AUTO, labelMajorMode: GaugeLabelMajorModes.AUTO, }, diff --git a/x-pack/plugins/lens/public/visualizations/gauge/utils.ts b/x-pack/plugins/lens/public/visualizations/gauge/utils.ts index 5c95e1205568..bbd8190c213c 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/utils.ts +++ b/x-pack/plugins/lens/public/visualizations/gauge/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ import type { Accessors } from '@kbn/expression-gauge-plugin/common'; -import { GaugeVisualizationState } from './constants'; +import type { GaugeVisualizationState } from './constants'; export const getAccessorsFromState = (state?: GaugeVisualizationState): Accessors | undefined => { const { minAccessor, maxAccessor, goalAccessor, metricAccessor } = state ?? {}; diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts index e999cac9dd0e..1ecedc1a63c4 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts @@ -11,14 +11,14 @@ import { createMockDatasource, createMockFramePublicAPI } from '../../mocks'; import { GROUP_ID } from './constants'; import type { DatasourceLayers, OperationDescriptor } from '../../types'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { GaugeVisualizationState } from './constants'; import { themeServiceMock } from '@kbn/core/public/mocks'; function exampleState(): GaugeVisualizationState { return { layerId: 'test-layer', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, labelMajorMode: 'auto', ticksPosition: 'auto', shape: 'horizontalBullet', @@ -39,7 +39,7 @@ describe('gauge', () => { test('returns a default state', () => { expect(getGaugeVisualization({ paletteService, theme }).initialize(() => 'l1')).toEqual({ layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: 'horizontalBullet', labelMajorMode: 'auto', ticksPosition: 'auto', @@ -523,7 +523,7 @@ describe('gauge', () => { paletteService, theme, }); - expect(instance.getLayerType('test-layer', state)).toEqual(layerTypes.DATA); + expect(instance.getLayerType('test-layer', state)).toEqual(LayerTypes.DATA); expect(instance.getLayerType('foo', state)).toBeUndefined(); }); }); diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx index 19fd46459b2b..4ea17a2136aa 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx @@ -23,6 +23,7 @@ import { getValueFromAccessor, } from '@kbn/expression-gauge-plugin/public'; import { IconChartHorizontalBullet, IconChartVerticalBullet } from '@kbn/chart-icons'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { DatasourceLayers, OperationMetadata, Visualization } from '../../types'; import { getSuggestions } from './suggestions'; import { @@ -34,7 +35,6 @@ import { import { GaugeToolbar } from './toolbar_component'; import { applyPaletteParams } from '../../shared_components'; import { GaugeDimensionEditor } from './dimension_editor'; -import { layerTypes } from '../../../common'; import { generateId } from '../../id_generator'; import { getAccessorsFromState } from './utils'; @@ -215,7 +215,7 @@ export const getGaugeVisualization = ({ return ( state || { layerId: addNewLayer(), - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: GaugeShapes.HORIZONTAL_BULLET, palette: mainPalette, ticksPosition: 'auto', @@ -432,7 +432,7 @@ export const getGaugeVisualization = ({ return [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: i18n.translate('xpack.lens.gauge.addLayer', { defaultMessage: 'Visualization', }), diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/heatmap/suggestions.test.ts index 70db8e0d4165..0fc9c6fc3067 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/suggestions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/suggestions.test.ts @@ -6,10 +6,10 @@ */ import { Position } from '@elastic/charts'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { getSuggestions } from './suggestions'; import type { HeatmapVisualizationState } from './types'; import { HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION } from './constants'; -import { layerTypes } from '../../../common'; describe('heatmap suggestions', () => { describe('rejects suggestions', () => { @@ -25,7 +25,7 @@ describe('heatmap suggestions', () => { state: { shape: 'heatmap', layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -72,7 +72,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -118,7 +118,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -173,7 +173,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -220,7 +220,7 @@ describe('heatmap suggestions', () => { state: { shape: 'heatmap', layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, xAccessor: 'some-field', } as HeatmapVisualizationState, keptLayerIds: ['first'], @@ -269,7 +269,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -277,7 +277,7 @@ describe('heatmap suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: 'heatmap', valueAccessor: 'metric-column', xAccessor: 'date-column-01', @@ -324,7 +324,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -332,7 +332,7 @@ describe('heatmap suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: 'heatmap', xAccessor: 'test-column', gridConfig: { @@ -377,7 +377,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -385,7 +385,7 @@ describe('heatmap suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: 'heatmap', valueAccessor: 'test-column', gridConfig: { @@ -440,7 +440,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -448,7 +448,7 @@ describe('heatmap suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: 'heatmap', xAccessor: 'date-column', valueAccessor: 'metric-column', @@ -506,7 +506,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -514,7 +514,7 @@ describe('heatmap suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: 'heatmap', xAccessor: 'number-column', valueAccessor: 'metric-column', @@ -579,7 +579,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -587,7 +587,7 @@ describe('heatmap suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: 'heatmap', yAccessor: 'date-column', xAccessor: 'number-column', @@ -653,7 +653,7 @@ describe('heatmap suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as HeatmapVisualizationState, keptLayerIds: ['first'], }) @@ -661,7 +661,7 @@ describe('heatmap suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, shape: 'heatmap', xAccessor: 'number-column', yAccessor: 'group-column', diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/suggestions.ts b/x-pack/plugins/lens/public/visualizations/heatmap/suggestions.ts index a302be4cfceb..aca5f4dfe689 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/suggestions.ts @@ -8,10 +8,10 @@ import { partition } from 'lodash'; import { Position } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { Visualization } from '../../types'; import type { HeatmapVisualizationState } from './types'; import { CHART_SHAPES, HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION } from './constants'; -import { layerTypes } from '../../../common'; export const getSuggestions: Visualization['getSuggestions'] = ({ table, @@ -81,7 +81,7 @@ export const getSuggestions: Visualization['getSugges const newState: HeatmapVisualizationState = { shape: CHART_SHAPES.HEATMAP, layerId: table.layerId, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, legend: { isVisible: state?.legend?.isVisible ?? true, position: state?.legend?.position ?? Position.Right, diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts index 38cfa9bf10b5..8f958b8fa75c 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts @@ -18,17 +18,17 @@ import { HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION, } from './constants'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { Position } from '@elastic/charts'; import type { HeatmapVisualizationState } from './types'; import type { DatasourceLayers, OperationDescriptor } from '../../types'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; -import { layerTypes } from '../../../common'; import { themeServiceMock } from '@kbn/core/public/mocks'; function exampleState(): HeatmapVisualizationState { return { layerId: 'test-layer', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, legend: { isVisible: true, position: Position.Right, @@ -62,7 +62,7 @@ describe('heatmap', () => { test('returns a default state', () => { expect(getHeatmapVisualization({ paletteService, theme }).initialize(() => 'l1')).toEqual({ layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, title: 'Empty Heatmap chart', shape: CHART_SHAPES.HEATMAP, legend: { @@ -358,7 +358,7 @@ describe('heatmap', () => { paletteService, theme, }); - expect(instance.getLayerType('test-layer', state)).toEqual(layerTypes.DATA); + expect(instance.getLayerType('test-layer', state)).toEqual(LayerTypes.DATA); expect(instance.getLayerType('foo', state)).toBeUndefined(); }); }); diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index 1fc48b4d71c3..b8e98d03843a 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -16,6 +16,7 @@ import { CUSTOM_PALETTE, PaletteRegistry, CustomPaletteParams } from '@kbn/color import { ThemeServiceStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { OperationMetadata, Visualization } from '../../types'; import type { HeatmapVisualizationState } from './types'; import { getSuggestions } from './suggestions'; @@ -32,7 +33,6 @@ import { import { HeatmapToolbar } from './toolbar_component'; import { HeatmapDimensionEditor } from './dimension_editor'; import { getSafePaletteParams } from './utils'; -import { layerTypes } from '../../../common'; const groupLabelForHeatmap = i18n.translate('xpack.lens.heatmapVisualization.heatmapGroupLabel', { defaultMessage: 'Magnitude', @@ -146,7 +146,7 @@ export const getHeatmapVisualization = ({ return ( state || { layerId: addNewLayer(), - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, title: 'Empty Heatmap chart', ...getInitialState(), } @@ -289,7 +289,7 @@ export const getHeatmapVisualization = ({ getSupportedLayers() { return [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: i18n.translate('xpack.lens.heatmap.addLayer', { defaultMessage: 'Visualization', }), diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.test.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.test.tsx index 560e4ed67b54..824f70dd6834 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.test.tsx @@ -22,7 +22,7 @@ import { import { act } from 'react-dom/test-utils'; import { PalettePanelContainer } from '../../shared_components'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { LegacyMetricState } from '../../../common/types'; // mocking random id generator function @@ -57,7 +57,7 @@ describe('metric dimension editor', () => { function testState(): LegacyMetricState { return { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, accessor: 'foo', }; } diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx index 5c2239a81c3e..84e0dc6f675b 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import React, { useCallback, useState } from 'react'; import { ColorMode } from '@kbn/charts-plugin/common'; import type { LegacyMetricState } from '../../../common/types'; -import { isNumericFieldForDatatable } from '../../../common/expressions'; +import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils'; import { applyPaletteParams, PalettePanelContainer } from '../../shared_components'; import type { VisualizationDimensionEditorProps } from '../../types'; import { defaultPaletteParams } from './palette_config'; diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/metric_suggestions.ts b/x-pack/plugins/lens/public/visualizations/legacy_metric/metric_suggestions.ts index 16f4d4890f47..c48e463ec83d 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/metric_suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/metric_suggestions.ts @@ -6,9 +6,9 @@ */ import { IconChartMetric } from '@kbn/chart-icons'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { SuggestionRequest, VisualizationSuggestion, TableSuggestion } from '../../types'; import type { LegacyMetricState } from '../../../common/types'; -import { layerTypes } from '../../../common'; import { legacyMetricSupportedTypes } from './visualization'; /** @@ -53,7 +53,7 @@ function getSuggestion(table: TableSuggestion): VisualizationSuggestion { expect(metricVisualization.clearLayer(exampleState(), 'l1', 'indexPattern1')).toEqual({ accessor: undefined, layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }); }); }); @@ -85,7 +85,7 @@ describe('metric_visualization', () => { state: { accessor: undefined, layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, layerId: 'l1', frame: mockFrame(), @@ -105,7 +105,7 @@ describe('metric_visualization', () => { state: { accessor: 'a', layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, layerId: 'l1', frame: mockFrame(), @@ -125,7 +125,7 @@ describe('metric_visualization', () => { state: { accessor: 'a', layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, palette: { type: 'palette', name: 'status', @@ -151,7 +151,7 @@ describe('metric_visualization', () => { state: { accessor: 'a', layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, layerId: 'l1', frame: mockFrame(), @@ -175,7 +175,7 @@ describe('metric_visualization', () => { prevState: { accessor: undefined, layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, layerId: 'l1', groupId: '', @@ -185,7 +185,7 @@ describe('metric_visualization', () => { ).toEqual({ accessor: 'newDimension', layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }); }); }); @@ -197,7 +197,7 @@ describe('metric_visualization', () => { prevState: { accessor: 'a', layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, layerId: 'l1', columnId: 'a', @@ -206,7 +206,7 @@ describe('metric_visualization', () => { ).toEqual({ accessor: undefined, layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, colorMode: ColorMode.None, palette: undefined, }); @@ -218,7 +218,7 @@ describe('metric_visualization', () => { prevState: { accessor: 'a', layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, colorMode: ColorMode.Background, palette: { type: 'palette', @@ -239,7 +239,7 @@ describe('metric_visualization', () => { ).toEqual({ accessor: undefined, layerId: 'l1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, colorMode: ColorMode.None, palette: undefined, }); @@ -254,7 +254,7 @@ describe('metric_visualization', () => { describe('#getLayerType', () => { it('should return the type only if the layer is in the state', () => { - expect(metricVisualization.getLayerType('l1', exampleState())).toEqual(layerTypes.DATA); + expect(metricVisualization.getLayerType('l1', exampleState())).toEqual(LayerTypes.DATA); expect(metricVisualization.getLayerType('foo', exampleState())).toBeUndefined(); }); }); diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx index 02a4cd23ad4f..37304e09523a 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx @@ -15,10 +15,10 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { ColorMode, CustomPaletteState } from '@kbn/charts-plugin/common'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { IconChartMetric } from '@kbn/chart-icons'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { getSuggestions } from './metric_suggestions'; import { Visualization, OperationMetadata, DatasourceLayers } from '../../types'; import type { LegacyMetricState } from '../../../common/types'; -import { layerTypes } from '../../../common'; import { MetricDimensionEditor } from './dimension_editor'; import { MetricToolbar } from './metric_config_panel'; import { DEFAULT_TITLE_POSITION } from './metric_config_panel/title_position_option'; @@ -222,7 +222,7 @@ export const getLegacyMetricVisualization = ({ state || { layerId: addNewLayer(), accessor: undefined, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } ); }, @@ -267,7 +267,7 @@ export const getLegacyMetricVisualization = ({ getSupportedLayers() { return [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: i18n.translate('xpack.lens.legacyMetric.addLayer', { defaultMessage: 'Visualization', }), diff --git a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx index f2188f2a356a..2f1ea5dc1350 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx @@ -31,7 +31,7 @@ import { import { getDataBoundsForPalette } from '@kbn/expression-metric-vis-plugin/public'; import { css } from '@emotion/react'; import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils'; -import { isNumericFieldForDatatable } from '../../../common/expressions'; +import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils'; import { applyPaletteParams, PalettePanelContainer, diff --git a/x-pack/plugins/lens/public/visualizations/metric/suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/metric/suggestions.test.ts index 45f332776a4d..22d701cee570 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/suggestions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/metric/suggestions.test.ts @@ -6,7 +6,7 @@ */ import { getSuggestions } from './suggestions'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { MetricVisualizationState } from './visualization'; import { IconChartMetric } from '@kbn/chart-icons'; @@ -125,7 +125,7 @@ describe('metric suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as MetricVisualizationState, keptLayerIds: ['first'], }; @@ -146,7 +146,7 @@ describe('metric suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as MetricVisualizationState, keptLayerIds: ['first'], }) @@ -154,7 +154,7 @@ describe('metric suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, metricAccessor: metricColumn.columnId, // should ignore bucketed column for initial drag }, @@ -177,7 +177,7 @@ describe('metric suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as MetricVisualizationState, keptLayerIds: ['first'], }) @@ -185,7 +185,7 @@ describe('metric suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, breakdownByAccessor: bucketColumn.columnId, }, title: 'Metric', @@ -207,7 +207,7 @@ describe('metric suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, metricAccessor: 'non_existent', } as MetricVisualizationState, keptLayerIds: ['first'], @@ -216,7 +216,7 @@ describe('metric suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, metricAccessor: undefined, breakdownByAccessor: bucketColumn.columnId, }, @@ -238,7 +238,7 @@ describe('metric suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, secondaryMetricAccessor: 'some-accessor', maxAccessor: 'some-accessor', } as MetricVisualizationState, @@ -260,7 +260,7 @@ describe('metric suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } as MetricVisualizationState, keptLayerIds: ['first'], }) @@ -280,7 +280,7 @@ describe('metric suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, breakdownByAccessor: bucketColumn.columnId, } as MetricVisualizationState, keptLayerIds: ['first'], @@ -289,7 +289,7 @@ describe('metric suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, metricAccessor: metricColumn.columnId, breakdownByAccessor: bucketColumn.columnId, }, @@ -312,7 +312,7 @@ describe('metric suggestions', () => { }, state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, metricAccessor: metricColumn.columnId, } as MetricVisualizationState, keptLayerIds: ['first'], @@ -321,7 +321,7 @@ describe('metric suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, metricAccessor: metricColumn.columnId, breakdownByAccessor: bucketColumn.columnId, }, @@ -352,7 +352,7 @@ describe('metric suggestions', () => { { state: { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, metricAccessor: metricColumn.columnId, breakdownByAccessor: bucketColumn.columnId, }, diff --git a/x-pack/plugins/lens/public/visualizations/metric/suggestions.ts b/x-pack/plugins/lens/public/visualizations/metric/suggestions.ts index c0354d4db65e..a4077b4aca45 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/metric/suggestions.ts @@ -6,8 +6,8 @@ */ import { IconChartMetric } from '@kbn/chart-icons'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { TableSuggestion, Visualization } from '../../types'; -import { layerTypes } from '../../../common'; import { metricLabel, MetricVisualizationState, supportedDataTypes } from './visualization'; const MAX_BUCKETED_COLUMNS = 1; @@ -56,7 +56,7 @@ export const getSuggestions: Visualization['getSuggest state: { ...state, layerId: table.layerId, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, title: metricLabel, previewIcon: IconChartMetric, diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts index 8171bd379237..87b4835187d3 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts @@ -9,7 +9,7 @@ import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; import { ExpressionAstExpression, ExpressionAstFunction } from '@kbn/expressions-plugin/common'; import { euiLightVars, euiThemeVars } from '@kbn/ui-theme'; -import { layerTypes } from '../..'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { createMockDatasource, createMockFramePublicAPI } from '../../mocks'; import { DatasourceLayers, @@ -61,7 +61,7 @@ describe('metric visualization', () => { test('returns a default state', () => { expect(visualization.initialize(() => 'some-id')).toEqual({ layerId: 'some-id', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }); }); diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx index 81f7f08fe322..e3b42fedc45e 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx @@ -17,7 +17,8 @@ import { LayoutDirection } from '@elastic/charts'; import { euiLightVars, euiThemeVars } from '@kbn/ui-theme'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { IconChartMetric } from '@kbn/chart-icons'; -import { LayerType } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; +import type { LayerType } from '../../../common'; import { getSuggestions } from './suggestions'; import { Visualization, @@ -26,7 +27,6 @@ import { AccessorConfig, Suggestion, } from '../../types'; -import { layerTypes } from '../../../common'; import { GROUP_ID, LENS_METRIC_ID } from './constants'; import { DimensionEditor } from './dimension_editor'; import { Toolbar } from './toolbar'; @@ -248,7 +248,7 @@ export const getMetricVisualization = ({ return ( state ?? { layerId: addNewLayer(), - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, palette: mainPalette, } ); @@ -402,7 +402,7 @@ export const getMetricVisualization = ({ getSupportedLayers(state) { return [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: i18n.translate('xpack.lens.metric.addLayer', { defaultMessage: 'Visualization', }), diff --git a/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts index dc0ce54b5bc0..c7285fb8771a 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts @@ -16,7 +16,7 @@ import { PieLayerState, PieVisualizationState, } from '../../../common'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; describe('suggestions', () => { describe('pie', () => { @@ -64,7 +64,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: [], metric: 'a', numberDisplay: NumberDisplay.HIDDEN, @@ -566,7 +566,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: ['a'], metric: 'b', @@ -590,7 +590,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: ['a'], metric: 'b', numberDisplay: NumberDisplay.HIDDEN, @@ -623,7 +623,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: [], metric: 'a', @@ -673,7 +673,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: ['a', 'b'], metric: 'e', numberDisplay: NumberDisplay.VALUE, @@ -722,7 +722,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: ['a', 'b'], metric: 'e', numberDisplay: NumberDisplay.PERCENT, @@ -759,7 +759,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: ['a'], metric: 'b', @@ -782,7 +782,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: ['a'], metric: 'b', @@ -816,7 +816,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: [], metric: 'a', @@ -858,7 +858,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: ['a', 'b'], metric: 'c', @@ -893,7 +893,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: [], metric: 'a', @@ -931,7 +931,7 @@ describe('suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: ['a', 'b'], metric: 'c', numberDisplay: NumberDisplay.HIDDEN, diff --git a/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts b/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts index 9f2d0920983a..25742f7cab03 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts @@ -7,6 +7,7 @@ import { partition } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { SuggestionRequest, TableSuggestionColumn, @@ -14,13 +15,12 @@ import type { } from '../../types'; import { CategoryDisplay, - layerTypes, LegendDisplay, NumberDisplay, PieChartTypes, PieVisualizationState, - isPartitionShape, } from '../../../common'; +import { isPartitionShape } from '../../../common/visualizations'; import type { PieChartType } from '../../../common/types'; import { PartitionChartsMeta } from './partition_charts_meta'; @@ -132,7 +132,7 @@ export function suggestions({ layerId: table.layerId, primaryGroups: groups.map((col) => col.columnId), metric: metricColumnId, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } : { layerId: table.layerId, @@ -142,7 +142,7 @@ export function suggestions({ categoryDisplay: CategoryDisplay.DEFAULT, legendDisplay: LegendDisplay.DEFAULT, nestedLegend: false, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, ], }, @@ -201,7 +201,7 @@ export function suggestions({ state.layers[0].categoryDisplay === CategoryDisplay.INSIDE ? CategoryDisplay.DEFAULT : state.layers[0].categoryDisplay, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } : { layerId: table.layerId, @@ -211,7 +211,7 @@ export function suggestions({ categoryDisplay: CategoryDisplay.DEFAULT, legendDisplay: LegendDisplay.DEFAULT, nestedLegend: false, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, ], }, @@ -246,7 +246,7 @@ export function suggestions({ secondaryGroups: groups[1] ? [groups[1].columnId] : [], metric: metricColumnId, categoryDisplay: CategoryDisplay.DEFAULT, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } : { layerId: table.layerId, @@ -257,7 +257,7 @@ export function suggestions({ categoryDisplay: CategoryDisplay.DEFAULT, legendDisplay: LegendDisplay.DEFAULT, nestedLegend: false, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, ], }, @@ -286,7 +286,7 @@ export function suggestions({ primaryGroups: groups.map((col) => col.columnId), metric: metricColumnId, categoryDisplay: CategoryDisplay.DEFAULT, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, } : { layerId: table.layerId, @@ -296,7 +296,7 @@ export function suggestions({ categoryDisplay: CategoryDisplay.DEFAULT, legendDisplay: LegendDisplay.DEFAULT, nestedLegend: false, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, ], }, diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts index 82073f1d5d60..25975ffb6ec7 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts @@ -13,7 +13,7 @@ import { NumberDisplay, LegendDisplay, } from '../../../common'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { createMockDatasource, createMockFramePublicAPI } from '../../mocks'; import { FramePublicAPI } from '../../types'; @@ -36,7 +36,7 @@ function getExampleState(): PieVisualizationState { layers: [ { layerId: LAYER_ID, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, primaryGroups: [], metric: undefined, numberDisplay: NumberDisplay.PERCENT, @@ -92,7 +92,7 @@ describe('pie_visualization', () => { describe('#getLayerType', () => { it('should return the type only if the layer is in the state', () => { - expect(pieVisualization.getLayerType(LAYER_ID, getExampleState())).toEqual(layerTypes.DATA); + expect(pieVisualization.getLayerType(LAYER_ID, getExampleState())).toEqual(LayerTypes.DATA); expect(pieVisualization.getLayerType('foo', getExampleState())).toBeUndefined(); }); }); @@ -104,7 +104,7 @@ describe('pie_visualization', () => { { primaryGroups: ['a'], layerId: LAYER_ID, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, numberDisplay: NumberDisplay.PERCENT, categoryDisplay: CategoryDisplay.DEFAULT, legendDisplay: LegendDisplay.DEFAULT, diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index 2a8bdc2dd4ab..49a485debcf2 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -15,6 +15,7 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { EuiSpacer } from '@elastic/eui'; import { PartitionVisConfiguration } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { Visualization, OperationMetadata, @@ -24,13 +25,19 @@ import type { VisualizeEditorContext, } from '../../types'; import { getSortedGroups, toExpression, toPreviewExpression } from './to_expression'; -import { CategoryDisplay, layerTypes, LegendDisplay, NumberDisplay } from '../../../common'; +import { + CategoryDisplay, + LegendDisplay, + NumberDisplay, + PieChartTypes, + PieLayerState, + PieVisualizationState, +} from '../../../common'; import { suggestions } from './suggestions'; import { PartitionChartsMeta } from './partition_charts_meta'; import { DimensionEditor, PieToolbar } from './toolbar'; import { checkTableForContainsSmallValues } from './render_helpers'; -import { PieChartTypes, PieLayerState, PieVisualizationState } from '../../../common'; -import { IndexPatternLayer } from '../..'; +import type { IndexPatternLayer } from '../..'; interface DatatableDatasourceState { [prop: string]: unknown; @@ -52,7 +59,7 @@ function newLayerState(layerId: string): PieLayerState { categoryDisplay: CategoryDisplay.DEFAULT, legendDisplay: LegendDisplay.DEFAULT, nestedLegend: false, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }; } @@ -362,7 +369,7 @@ export const getPieVisualization = ({ getSupportedLayers() { return [ { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: i18n.translate('xpack.lens.pie.addLayer', { defaultMessage: 'Visualization', }), diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx index 990d0dd8f94d..7d2940f5b0df 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx @@ -15,8 +15,8 @@ import { } from '@kbn/event-annotation-plugin/public'; import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common'; import { IconChartBarAnnotations } from '@kbn/chart-icons'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { isDraggedDataViewField } from '../../../utils'; -import { layerTypes } from '../../../../common'; import type { FramePublicAPI, Visualization } from '../../../types'; import { isHorizontalChart } from '../state_helpers'; import type { XYState, XYDataLayerConfig, XYAnnotationLayerConfig, XYLayerConfig } from '../types'; @@ -111,7 +111,7 @@ export const getAnnotationsSupportedLayer = ( : undefined; return { - type: layerTypes.ANNOTATIONS, + type: LayerTypes.ANNOTATIONS, label: i18n.translate('xpack.lens.xyChart.addAnnotationsLayerLabel', { defaultMessage: 'Annotations', }), diff --git a/x-pack/plugins/lens/public/visualizations/xy/axes_configuration.test.ts b/x-pack/plugins/lens/public/visualizations/xy/axes_configuration.test.ts index 568cd38635ac..a351b9eb9e90 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/axes_configuration.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/axes_configuration.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { Datatable } from '@kbn/expressions-plugin/public'; import { getAxesConfiguration } from './axes_configuration'; import { XYDataLayerConfig } from './types'; @@ -221,7 +221,7 @@ describe('axes_configuration', () => { const sampleLayer: XYDataLayerConfig = { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'line', xAccessor: 'c', accessors: ['yAccessorId'], diff --git a/x-pack/plugins/lens/public/visualizations/xy/color_assignment.test.ts b/x-pack/plugins/lens/public/visualizations/xy/color_assignment.test.ts index b59aadda1fdf..e359b85d1339 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/color_assignment.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/color_assignment.test.ts @@ -7,7 +7,7 @@ import { getColorAssignments } from './color_assignment'; import type { FormatFactory } from '../../../common'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { XYDataLayerConfig } from './types'; import { Datatable } from '@kbn/expressions-plugin/common'; @@ -17,7 +17,7 @@ describe('color_assignment', () => { seriesType: 'bar', palette: { type: 'palette', name: 'palette1' }, layerId: '1', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, splitAccessor: 'split1', accessors: ['y1', 'y2'], }, @@ -25,7 +25,7 @@ describe('color_assignment', () => { seriesType: 'bar', palette: { type: 'palette', name: 'palette2' }, layerId: '2', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, splitAccessor: 'split2', accessors: ['y3', 'y4'], }, diff --git a/x-pack/plugins/lens/public/visualizations/xy/reference_line_helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/reference_line_helpers.tsx index 84f0a35d3b95..6e1763841e70 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/reference_line_helpers.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/reference_line_helpers.tsx @@ -9,7 +9,7 @@ import { groupBy, partition } from 'lodash'; import { i18n } from '@kbn/i18n'; import { Datatable } from '@kbn/expressions-plugin/public'; import { IconChartBarReferenceLine } from '@kbn/chart-icons'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import type { DatasourceLayers, FramePublicAPI, Visualization } from '../../types'; import { groupAxesByType } from './axes_configuration'; import { isHorizontalChart, isPercentageSeries, isStackedChart } from './state_helpers'; @@ -314,7 +314,7 @@ export const getReferenceSupportedLayer = ( : undefined; return { - type: layerTypes.REFERENCELINE, + type: LayerTypes.REFERENCELINE, label: i18n.translate('xpack.lens.xyChart.addReferenceLineLayerLabel', { defaultMessage: 'Reference lines', }), diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts index d66d49e1ba9b..79821d2fc423 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts @@ -11,7 +11,7 @@ import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { getXyVisualization, XYState } from './xy_visualization'; import { OperationDescriptor } from '../../types'; import { createMockDatasource, createMockFramePublicAPI } from '../../mocks'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks'; import { defaultReferenceLineColor } from './color_assignment'; @@ -102,7 +102,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -128,7 +128,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -153,7 +153,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -193,7 +193,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: undefined, xAccessor: undefined, @@ -220,7 +220,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: undefined, xAccessor: 'a', @@ -244,7 +244,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -280,7 +280,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -320,7 +320,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -360,7 +360,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -400,7 +400,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -424,7 +424,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -455,7 +455,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -486,7 +486,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -512,7 +512,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -521,7 +521,7 @@ describe('#toExpression', () => { }, { layerId: 'referenceLine', - layerType: layerTypes.REFERENCELINE, + layerType: LayerTypes.REFERENCELINE, accessors: ['b', 'c'], yConfig: [{ forAccessor: 'a' }], }, @@ -550,7 +550,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -559,7 +559,7 @@ describe('#toExpression', () => { }, { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, annotations: [], indexPatternId: 'my-indexPattern', ignoreGlobalFilters: true, @@ -582,7 +582,7 @@ describe('#toExpression', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts index bf75018f111e..b57e866088e2 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts @@ -16,16 +16,17 @@ import { import { LegendSize } from '@kbn/visualizations-plugin/public'; import { XYCurveType } from '@kbn/expression-xy-plugin/common'; import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common'; -import { +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; +import type { State, YConfig, XYDataLayerConfig, XYReferenceLineLayerConfig, XYAnnotationLayerConfig, AxisConfig, + ValidXYDataLayerConfig, } from './types'; -import type { ValidXYDataLayerConfig } from './types'; -import { OperationMetadata, DatasourcePublicAPI, DatasourceLayers } from '../../types'; +import type { OperationMetadata, DatasourcePublicAPI, DatasourceLayers } from '../../types'; import { getColumnToLabelMap } from './state_helpers'; import { hasIcon } from './xy_config_panel/shared/icon_select'; import { defaultReferenceLineColor } from './color_assignment'; @@ -37,7 +38,6 @@ import { getAnnotationsLayers, } from './visualization_helpers'; import { getUniqueLabels } from './annotations/helpers'; -import { layerTypes } from '../../../common'; import { axisExtentConfigToExpression } from '../../shared_components'; export const getSortedAccessors = ( @@ -90,8 +90,8 @@ export const toExpression = ( }; const simplifiedLayerExpression = { - [layerTypes.DATA]: (layer: XYDataLayerConfig) => ({ ...layer, simpleView: true }), - [layerTypes.REFERENCELINE]: (layer: XYReferenceLineLayerConfig) => ({ + [LayerTypes.DATA]: (layer: XYDataLayerConfig) => ({ ...layer, simpleView: true }), + [LayerTypes.REFERENCELINE]: (layer: XYReferenceLineLayerConfig) => ({ ...layer, simpleView: true, yConfig: layer.yConfig?.map(({ ...rest }) => ({ @@ -101,7 +101,7 @@ const simplifiedLayerExpression = { textVisibility: false, })), }), - [layerTypes.ANNOTATIONS]: (layer: XYAnnotationLayerConfig) => ({ + [LayerTypes.ANNOTATIONS]: (layer: XYAnnotationLayerConfig) => ({ ...layer, simpleView: true, }), diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts index e76633b34992..6c2d61d7bb86 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts @@ -16,7 +16,7 @@ import type { XYReferenceLineLayerConfig, SeriesType, } from './types'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { createMockDatasource, createMockFramePublicAPI } from '../../mocks'; import { IconChartBar } from '@kbn/chart-icons'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; @@ -61,7 +61,7 @@ function exampleState(): XYState { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -221,7 +221,7 @@ describe('xy_visualization', () => { ...exampleState().layers, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'e', xAccessor: 'f', @@ -239,7 +239,7 @@ describe('xy_visualization', () => { const layers = xyVisualization.appendLayer!( exampleState(), 'foo', - layerTypes.DATA, + LayerTypes.DATA, 'indexPattern1' ).layers; expect(layers.length).toEqual(exampleState().layers.length + 1); @@ -328,7 +328,7 @@ describe('xy_visualization', () => { describe('#getLayerType', () => { it('should return the type only if the layer is in the state', () => { - expect(xyVisualization.getLayerType('first', exampleState())).toEqual(layerTypes.DATA); + expect(xyVisualization.getLayerType('first', exampleState())).toEqual(LayerTypes.DATA); expect(xyVisualization.getLayerType('foo', exampleState())).toBeUndefined(); }); }); @@ -377,7 +377,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: [], @@ -390,7 +390,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'newCol', accessors: [], @@ -406,7 +406,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], @@ -419,7 +419,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'newCol', accessors: [], @@ -435,7 +435,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'referenceLine', - layerType: layerTypes.REFERENCELINE, + layerType: LayerTypes.REFERENCELINE, accessors: [], }, ], @@ -446,7 +446,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'referenceLine', - layerType: layerTypes.REFERENCELINE, + layerType: LayerTypes.REFERENCELINE, accessors: ['newCol'], yConfig: [ { @@ -467,7 +467,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, @@ -480,7 +480,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', ignoreGlobalFilters: true, annotations: [ @@ -716,7 +716,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], ignoreGlobalFilters: true, @@ -745,7 +745,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [ exampleAnnotation2, @@ -776,7 +776,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, @@ -805,7 +805,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [ { @@ -836,7 +836,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], ignoreGlobalFilters: true, @@ -862,7 +862,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2, { ...exampleAnnotation2, id: 'newColId' }], ignoreGlobalFilters: true, @@ -877,7 +877,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation, exampleAnnotation2], ignoreGlobalFilters: true, @@ -904,7 +904,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2, exampleAnnotation], ignoreGlobalFilters: true, @@ -920,14 +920,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, }, { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], ignoreGlobalFilters: true, @@ -955,14 +955,14 @@ describe('xy_visualization', () => { ).toEqual([ { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, }, { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [{ ...exampleAnnotation, id: 'an2' }], ignoreGlobalFilters: true, @@ -978,14 +978,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, }, { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], ignoreGlobalFilters: true, @@ -1013,14 +1013,14 @@ describe('xy_visualization', () => { ).toEqual([ { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], ignoreGlobalFilters: true, }, { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, @@ -1036,14 +1036,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, }, { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], ignoreGlobalFilters: true, @@ -1070,14 +1070,14 @@ describe('xy_visualization', () => { ).toEqual([ { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [], ignoreGlobalFilters: true, }, { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, @@ -1093,14 +1093,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, }, { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [], ignoreGlobalFilters: true, @@ -1128,14 +1128,14 @@ describe('xy_visualization', () => { ).toEqual([ { layerId: 'first', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [], ignoreGlobalFilters: true, }, { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, @@ -1183,7 +1183,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], @@ -1195,7 +1195,7 @@ describe('xy_visualization', () => { }).layers[0] ).toEqual({ layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: [], @@ -1210,14 +1210,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], }, { layerId: 'ann', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation, { ...exampleAnnotation, id: 'an2' }], ignoreGlobalFilters: true, @@ -1230,14 +1230,14 @@ describe('xy_visualization', () => { ).toEqual([ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], }, { layerId: 'ann', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, @@ -1533,7 +1533,7 @@ describe('xy_visualization', () => { ...baseState.layers[0], accessors: ['e'], seriesType: 'bar_percentage_stacked', - layerType: layerTypes.REFERENCELINE, + layerType: LayerTypes.REFERENCELINE, }, ], ], @@ -1600,7 +1600,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: undefined, xAccessor: undefined, @@ -1608,7 +1608,7 @@ describe('xy_visualization', () => { }, { layerId: 'referenceLine', - layerType: layerTypes.REFERENCELINE, + layerType: LayerTypes.REFERENCELINE, accessors: [], yConfig: [{ axisMode: 'left', forAccessor: 'a' }], }, @@ -1950,7 +1950,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: undefined, xAccessor: 'a', @@ -1958,7 +1958,7 @@ describe('xy_visualization', () => { }, { layerId: 'annotations', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], ignoreGlobalFilters: true, @@ -2182,7 +2182,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], @@ -2198,14 +2198,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], @@ -2221,14 +2221,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: ['a'], }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: ['a'], @@ -2245,7 +2245,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: [], @@ -2261,7 +2261,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: [], @@ -2269,7 +2269,7 @@ describe('xy_visualization', () => { }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: [], @@ -2286,14 +2286,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: ['a'], @@ -2314,14 +2314,14 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: ['a'], }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: [], @@ -2329,7 +2329,7 @@ describe('xy_visualization', () => { }, { layerId: 'third', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: undefined, accessors: [], @@ -2351,21 +2351,21 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: [], }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: ['a'], }, { layerId: 'third', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: ['a'], @@ -2388,7 +2388,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -2436,7 +2436,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -2444,7 +2444,7 @@ describe('xy_visualization', () => { }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'e', @@ -2458,7 +2458,7 @@ describe('xy_visualization', () => { { shortMessage: 'Wrong data type for Horizontal axis.', longMessage: - 'Data type mismatch for the Horizontal axis. Cannot mix date and number interval types.', + 'The Horizontal axis data in layer 1 is incompatible with the data in layer 2. Select a new function for the Horizontal axis.', }, ]); }); @@ -2492,7 +2492,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'a', @@ -2500,7 +2500,7 @@ describe('xy_visualization', () => { }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', splitAccessor: 'd', xAccessor: 'e', @@ -2513,7 +2513,8 @@ describe('xy_visualization', () => { ).toEqual([ { shortMessage: 'Wrong data type for Horizontal axis.', - longMessage: 'Data type mismatch for the Horizontal axis, use a different function.', + longMessage: + 'The Horizontal axis data in layer 1 is incompatible with the data in layer 2. Select a new function for the Horizontal axis.', }, ]); }); @@ -2669,7 +2670,7 @@ describe('xy_visualization', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'area', xAccessor: 'a', accessors: ['b'], @@ -2789,7 +2790,7 @@ describe('xy_visualization', () => { ...baseState.layers, { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, annotations: [exampleAnnotation2], ignoreGlobalFilters: true, }, @@ -2809,7 +2810,7 @@ describe('xy_visualization', () => { ...baseState.layers, { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], ignoreGlobalFilters: true, @@ -2828,7 +2829,7 @@ describe('xy_visualization', () => { ...baseState.layers, { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, annotations: [exampleAnnotation2], ignoreGlobalFilters: true, }, @@ -2848,7 +2849,7 @@ describe('xy_visualization', () => { ...baseState.layers, { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], ignoreGlobalFilters: true, @@ -2876,7 +2877,7 @@ describe('xy_visualization', () => { ...baseState.layers, { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, annotations: [exampleAnnotation2], ignoreGlobalFilters: true, indexPatternId: 'myIndexPattern', @@ -2908,7 +2909,7 @@ describe('xy_visualization', () => { ...baseState.layers, { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, annotations: [exampleAnnotation2], ignoreGlobalFilters: true, indexPatternId: 'myIndexPattern', @@ -2924,7 +2925,7 @@ describe('xy_visualization', () => { layers: expect.arrayContaining([ { layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, annotations: [exampleAnnotation2], ignoreGlobalFilters: false, indexPatternId: 'myIndexPattern', diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index c013f0cd1d07..583f0f431610 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -13,12 +13,13 @@ import { i18n } from '@kbn/i18n'; import type { PaletteRegistry } from '@kbn/coloring'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { CoreStart, ThemeServiceStart } from '@kbn/core/public'; -import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'; +import type { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { generateId } from '../../id_generator'; import { isDraggedDataViewField, @@ -30,7 +31,7 @@ import { getSuggestions } from './xy_suggestions'; import { XyToolbar } from './xy_config_panel'; import { DimensionEditor } from './xy_config_panel/dimension_editor'; import { LayerHeader, LayerHeaderContent } from './xy_config_panel/layer_header'; -import { Visualization, AccessorConfig, FramePublicAPI } from '../../types'; +import type { Visualization, AccessorConfig, FramePublicAPI } from '../../types'; import { type State, type XYLayerConfig, @@ -40,7 +41,6 @@ import { type PersistedState, visualizationTypes, } from './types'; -import { layerTypes } from '../../../common'; import { extractReferences, injectReferences, @@ -126,7 +126,7 @@ export const getXyVisualization = ({ }, getRemoveOperation(state, layerId) { - const dataLayers = getLayersByType(state, layerTypes.DATA).map((l) => l.layerId); + const dataLayers = getLayersByType(state, LayerTypes.DATA).map((l) => l.layerId); return dataLayers.includes(layerId) && dataLayers.length === 1 ? 'clear' : 'remove'; }, @@ -222,7 +222,7 @@ export const getXyVisualization = ({ position: Position.Top, seriesType: defaultSeriesType, showGridlines: false, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }, ], } diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx index 7aaa1c93f8fd..5354c392d357 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { uniq } from 'lodash'; import { IconChartBarHorizontal, IconChartBarStacked, IconChartMixedXy } from '@kbn/chart-icons'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { DatasourceLayers, OperationMetadata, VisualizationType } from '../../types'; import { State, @@ -20,8 +21,7 @@ import { SeriesType, } from './types'; import { isHorizontalChart } from './state_helpers'; -import { layerTypes } from '../..'; -import { LayerType } from '../../../common'; +import type { LayerType } from '../../../common'; export function getAxisName( axis: 'x' | 'y' | 'yLeft' | 'yRight', @@ -64,46 +64,41 @@ export function getAxisName( export function checkXAccessorCompatibility(state: XYState, datasourceLayers: DatasourceLayers) { const dataLayers = getDataLayers(state.layers); const errors = []; - const hasDateHistogramSet = dataLayers.some( + const hasDateHistogramSetIndex = dataLayers.findIndex( checkScaleOperation('interval', 'date', datasourceLayers) ); - const hasNumberHistogram = dataLayers.some( + const hasNumberHistogramIndex = dataLayers.findIndex( checkScaleOperation('interval', 'number', datasourceLayers) ); - const hasOrdinalAxis = dataLayers.some( + const hasOrdinalAxisIndex = dataLayers.findIndex( checkScaleOperation('ordinal', undefined, datasourceLayers) ); - if (state.layers.length > 1 && hasDateHistogramSet && hasNumberHistogram) { - errors.push({ - shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', { - defaultMessage: `Wrong data type for {axis}.`, - values: { - axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), - }, - }), - longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXLong', { - defaultMessage: `Data type mismatch for the {axis}. Cannot mix date and number interval types.`, - values: { - axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), - }, - }), - }); - } - if (state.layers.length > 1 && (hasDateHistogramSet || hasNumberHistogram) && hasOrdinalAxis) { - errors.push({ - shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', { - defaultMessage: `Wrong data type for {axis}.`, - values: { - axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), - }, - }), - longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXOrdinalLong', { - defaultMessage: `Data type mismatch for the {axis}, use a different function.`, - values: { - axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), - }, - }), - }); + if (state.layers.length > 1) { + const erroredLayers = [hasDateHistogramSetIndex, hasNumberHistogramIndex, hasOrdinalAxisIndex] + .filter((v) => v >= 0) + .sort((a, b) => a - b); + if (erroredLayers.length > 1) { + const [firstLayer, ...otherLayers] = erroredLayers; + const axis = getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }); + for (const otherLayer of otherLayers) { + errors.push({ + shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', { + defaultMessage: `Wrong data type for {axis}.`, + values: { + axis, + }, + }), + longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXLong', { + defaultMessage: `The {axis} data in layer {firstLayer} is incompatible with the data in layer {secondLayer}. Select a new function for the {axis}.`, + values: { + axis, + firstLayer: firstLayer + 1, + secondLayer: otherLayer + 1, + }, + }), + }); + } + } } return errors; } @@ -126,7 +121,7 @@ export function checkScaleOperation( } export const isDataLayer = (layer: XYLayerConfig): layer is XYDataLayerConfig => - layer.layerType === layerTypes.DATA || !layer.layerType; + layer.layerType === LayerTypes.DATA || !layer.layerType; export const getDataLayers = (layers: XYLayerConfig[]) => (layers || []).filter((layer): layer is XYDataLayerConfig => isDataLayer(layer)); @@ -136,31 +131,31 @@ export const getFirstDataLayer = (layers: XYLayerConfig[]) => export const isReferenceLayer = ( layer: Pick -): layer is XYReferenceLineLayerConfig => layer.layerType === layerTypes.REFERENCELINE; +): layer is XYReferenceLineLayerConfig => layer.layerType === LayerTypes.REFERENCELINE; export const getReferenceLayers = (layers: Array>) => (layers || []).filter((layer): layer is XYReferenceLineLayerConfig => isReferenceLayer(layer)); export const isAnnotationsLayer = ( layer: Pick -): layer is XYAnnotationLayerConfig => layer.layerType === layerTypes.ANNOTATIONS; +): layer is XYAnnotationLayerConfig => layer.layerType === LayerTypes.ANNOTATIONS; export const getAnnotationsLayers = (layers: Array>) => (layers || []).filter((layer): layer is XYAnnotationLayerConfig => isAnnotationsLayer(layer)); export interface LayerTypeToLayer { - [layerTypes.DATA]: (layer: XYDataLayerConfig) => XYDataLayerConfig; - [layerTypes.REFERENCELINE]: (layer: XYReferenceLineLayerConfig) => XYReferenceLineLayerConfig; - [layerTypes.ANNOTATIONS]: (layer: XYAnnotationLayerConfig) => XYAnnotationLayerConfig; + [LayerTypes.DATA]: (layer: XYDataLayerConfig) => XYDataLayerConfig; + [LayerTypes.REFERENCELINE]: (layer: XYReferenceLineLayerConfig) => XYReferenceLineLayerConfig; + [LayerTypes.ANNOTATIONS]: (layer: XYAnnotationLayerConfig) => XYAnnotationLayerConfig; } export const getLayerTypeOptions = (layer: XYLayerConfig, options: LayerTypeToLayer) => { if (isDataLayer(layer)) { - return options[layerTypes.DATA](layer); + return options[LayerTypes.DATA](layer); } else if (isReferenceLayer(layer)) { - return options[layerTypes.REFERENCELINE](layer); + return options[LayerTypes.REFERENCELINE](layer); } - return options[layerTypes.ANNOTATIONS](layer); + return options[LayerTypes.ANNOTATIONS](layer); }; export function getVisualizationType(state: State): VisualizationType | 'mixed' { @@ -216,7 +211,7 @@ export const defaultIcon = IconChartBarStacked; export const defaultSeriesType = 'bar_stacked'; export const supportedDataLayer = { - type: layerTypes.DATA, + type: LayerTypes.DATA, label: i18n.translate('xpack.lens.xyChart.addDataLayerLabel', { defaultMessage: 'Visualization', }), @@ -258,7 +253,7 @@ export function getMessageIdsForDimension( } const newLayerFn = { - [layerTypes.DATA]: ({ + [LayerTypes.DATA]: ({ layerId, seriesType, }: { @@ -266,16 +261,16 @@ const newLayerFn = { seriesType: SeriesType; }): XYDataLayerConfig => ({ layerId, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, accessors: [], seriesType, }), - [layerTypes.REFERENCELINE]: ({ layerId }: { layerId: string }): XYReferenceLineLayerConfig => ({ + [LayerTypes.REFERENCELINE]: ({ layerId }: { layerId: string }): XYReferenceLineLayerConfig => ({ layerId, - layerType: layerTypes.REFERENCELINE, + layerType: LayerTypes.REFERENCELINE, accessors: [], }), - [layerTypes.ANNOTATIONS]: ({ + [LayerTypes.ANNOTATIONS]: ({ layerId, indexPatternId, }: { @@ -283,7 +278,7 @@ const newLayerFn = { indexPatternId: string; }): XYAnnotationLayerConfig => ({ layerId, - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, annotations: [], indexPatternId, ignoreGlobalFilters: true, @@ -292,7 +287,7 @@ const newLayerFn = { export function newLayerState({ layerId, - layerType = layerTypes.DATA, + layerType = LayerTypes.DATA, seriesType, indexPatternId, }: { @@ -305,7 +300,7 @@ export function newLayerState({ } export function getLayersByType(state: State, byType?: string) { - return state.layers.filter(({ layerType = layerTypes.DATA }) => + return state.layers.filter(({ layerType = LayerTypes.DATA }) => byType ? layerType === byType : true ); } diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx index c852759a97aa..7d581d50d085 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { AnnotationsPanel } from '.'; import { FramePublicAPI } from '../../../../types'; -import { layerTypes } from '../../../..'; import { createMockFramePublicAPI } from '../../../../mocks'; import { State } from '../../types'; import { Position } from '@elastic/charts'; @@ -59,7 +59,7 @@ describe('AnnotationsPanel', () => { preferredSeriesType: 'bar', layers: [ { - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, layerId: 'annotation', indexPatternId: 'indexPattern1', annotations: [customLineStaticAnnotation], diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.test.tsx index 481987520ea2..3cbfbf3b84c8 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { AxisSettingsPopover, AxisSettingsPopoverProps } from './axis_settings_popover'; import { ToolbarPopover } from '../../../shared_components'; -import { layerTypes } from '../../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { ShallowWrapper } from 'enzyme'; function getRangeInputComponent(component: ShallowWrapper) { @@ -29,7 +29,7 @@ describe('Axes Settings', () => { layers: [ { seriesType: 'bar', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, layerId: 'first', splitAccessor: 'baz', xAccessor: 'foo', diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx index 8fd65fcd60f8..0e29a5fa6634 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx @@ -15,7 +15,7 @@ import { VisualOptionsPopover } from '.'; import { ToolbarPopover, ValueLabelsSettings } from '../../../../shared_components'; import { MissingValuesOptions } from './missing_values_option'; import { FillOpacityOption } from './fill_opacity_option'; -import { layerTypes } from '../../../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; describe('Visual options popover', () => { let frame: FramePublicAPI; @@ -28,7 +28,7 @@ describe('Visual options popover', () => { layers: [ { seriesType: 'bar', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, layerId: 'first', splitAccessor: 'baz', xAccessor: 'foo', @@ -234,7 +234,7 @@ describe('Visual options popover', () => { { ...state.layers[0], seriesType: 'bar' } as XYLayerConfig, { seriesType: 'bar', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, layerId: 'second', splitAccessor: 'baz', xAccessor: 'foo', diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx index 82e979c245df..088ba4537393 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx @@ -18,7 +18,7 @@ import { Position } from '@elastic/charts'; import { createMockFramePublicAPI, createMockDatasource } from '../../../mocks'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { EuiColorPicker } from '@elastic/eui'; -import { layerTypes } from '../../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { act } from 'react-dom/test-utils'; jest.mock('lodash', () => { @@ -41,7 +41,7 @@ describe('XY Config panels', () => { layers: [ { seriesType: 'bar', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, layerId: 'first', splitAccessor: 'baz', xAccessor: 'foo', @@ -315,7 +315,7 @@ describe('XY Config panels', () => { layers: [ { seriesType: 'bar', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, layerId: 'first', splitAccessor: undefined, xAccessor: 'foo', @@ -356,7 +356,7 @@ describe('XY Config panels', () => { layers: [ { seriesType: 'bar', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, layerId: 'first', splitAccessor: undefined, xAccessor: 'foo', @@ -399,7 +399,7 @@ describe('XY Config panels', () => { layers: [ { seriesType: 'bar', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, layerId: 'first', splitAccessor: undefined, xAccessor: 'foo', diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts index ff04ff46ce0f..399368ffb417 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts @@ -19,7 +19,7 @@ import { getXyVisualization } from './xy_visualization'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks'; import type { PaletteOutput } from '@kbn/coloring'; -import { layerTypes } from '../../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; @@ -209,14 +209,14 @@ describe('xy_suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', accessors: ['bytes'], splitAccessor: undefined, }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', accessors: ['bytes'], splitAccessor: undefined, @@ -315,7 +315,7 @@ describe('xy_suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', xAccessor: 'date', accessors: ['bytes'], @@ -357,7 +357,7 @@ describe('xy_suggestions', () => { layers: [ { layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', xAccessor: 'date', accessors: ['bytes'], @@ -365,7 +365,7 @@ describe('xy_suggestions', () => { }, { layerId: 'second', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', xAccessor: undefined, accessors: [], @@ -550,7 +550,7 @@ describe('xy_suggestions', () => { test('passes annotation layer for date histogram data layer', () => { const annotationLayer: XYAnnotationLayerConfig = { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', ignoreGlobalFilters: true, annotations: [ @@ -574,7 +574,7 @@ describe('xy_suggestions', () => { { accessors: ['price'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: 'product', xAccessor: 'date', @@ -597,7 +597,7 @@ describe('xy_suggestions', () => { expect(suggestion.state.layers).toEqual( expect.arrayContaining([ expect.objectContaining({ - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, }), ]) ) @@ -607,7 +607,7 @@ describe('xy_suggestions', () => { test('does not pass annotation layer if x-axis is not date histogram', () => { const annotationLayer: XYAnnotationLayerConfig = { layerId: 'second', - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', ignoreGlobalFilters: true, annotations: [ @@ -633,7 +633,7 @@ describe('xy_suggestions', () => { layerId: 'first', accessors: ['price'], seriesType: 'bar', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, xAccessor: 'date', splitAccessor: 'price2', }, @@ -655,7 +655,7 @@ describe('xy_suggestions', () => { expect(suggestion.state.layers).toEqual( expect.arrayContaining([ expect.not.objectContaining({ - layerType: layerTypes.ANNOTATIONS, + layerType: LayerTypes.ANNOTATIONS, }), ]) ) @@ -710,7 +710,7 @@ describe('xy_suggestions', () => { { accessors: ['price', 'quantity'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: 'product', xAccessor: 'date', @@ -765,7 +765,7 @@ describe('xy_suggestions', () => { { accessors: [], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'line', splitAccessor: undefined, xAccessor: '', @@ -804,7 +804,7 @@ describe('xy_suggestions', () => { { accessors: ['price'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: undefined, xAccessor: 'date', @@ -847,7 +847,7 @@ describe('xy_suggestions', () => { { accessors: ['price', 'quantity'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: 'product', xAccessor: 'date', @@ -891,7 +891,7 @@ describe('xy_suggestions', () => { { accessors: ['price', 'quantity'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: 'dummyCol', xAccessor: 'product', @@ -929,7 +929,7 @@ describe('xy_suggestions', () => { { accessors: ['price'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: 'date', xAccessor: 'product', @@ -970,7 +970,7 @@ describe('xy_suggestions', () => { { accessors: ['price', 'quantity'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: 'dummyCol', xAccessor: 'product', @@ -1015,7 +1015,7 @@ describe('xy_suggestions', () => { { accessors: ['price'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: 'category', xAccessor: 'product', @@ -1061,7 +1061,7 @@ describe('xy_suggestions', () => { { accessors: ['price', 'quantity'], layerId: 'first', - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, seriesType: 'bar', splitAccessor: 'dummyCol', xAccessor: 'product', diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts index b63bfeb5fbd9..3e6904ecb5ba 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts @@ -9,7 +9,8 @@ import { i18n } from '@kbn/i18n'; import { partition } from 'lodash'; import { Position } from '@elastic/charts'; import type { PaletteOutput } from '@kbn/coloring'; -import { +import { LayerTypes } from '@kbn/expression-xy-plugin/public'; +import type { SuggestionRequest, VisualizationSuggestion, TableSuggestionColumn, @@ -24,7 +25,6 @@ import { XYDataLayerConfig, SeriesType, } from './types'; -import { layerTypes } from '../../../common'; import { getIconForSeries } from './state_helpers'; import { getDataLayers, isDataLayer } from './visualization_helpers'; @@ -526,7 +526,7 @@ function buildSuggestion({ existingLayer && 'yConfig' in existingLayer && existingLayer.yConfig ? existingLayer.yConfig.filter(({ forAccessor }) => accessors.indexOf(forAccessor) !== -1) : undefined, - layerType: layerTypes.DATA, + layerType: LayerTypes.DATA, }; const hasDateHistogramDomain = @@ -539,7 +539,7 @@ function buildSuggestion({ .filter( (layer) => keptLayerIds.includes(layer.layerId) || - (hasDateHistogramDomain && layer.layerType === layerTypes.ANNOTATIONS) + (hasDateHistogramDomain && layer.layerType === LayerTypes.ANNOTATIONS) ) // Update in place .map((layer) => (layer.layerId === layerId ? newLayer : layer)) diff --git a/x-pack/plugins/lens/server/migrations/common_migrations.ts b/x-pack/plugins/lens/server/migrations/common_migrations.ts index 7fd65d44bb0b..4afe60729920 100644 --- a/x-pack/plugins/lens/server/migrations/common_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/common_migrations.ts @@ -7,6 +7,7 @@ import { cloneDeep, mapValues } from 'lodash'; import type { PaletteOutput, CustomPaletteParams } from '@kbn/coloring'; +import { LayerTypes } from '@kbn/expression-xy-plugin/common'; import { SerializableRecord } from '@kbn/utility-types'; import { mergeMigrationFunctionMaps, @@ -34,7 +35,8 @@ import { VisState850, LensDocShape850, } from './types'; -import { DOCUMENT_FIELD_NAME, layerTypes, LegacyMetricState, isPartitionShape } from '../../common'; +import { DOCUMENT_FIELD_NAME, LegacyMetricState } from '../../common'; +import { isPartitionShape } from '../../common/visualizations'; import { LensDocShape } from './saved_object_migrations'; export const commonRenameOperationsForFormula = ( @@ -110,11 +112,11 @@ export const commonUpdateVisLayerType = ( const newAttributes = cloneDeep(attributes); const visState = (newAttributes as LensDocShape715).state.visualization; if ('layerId' in visState) { - visState.layerType = layerTypes.DATA; + visState.layerType = LayerTypes.DATA; } if ('layers' in visState) { for (const layer of visState.layers) { - layer.layerType = layerTypes.DATA; + layer.layerType = LayerTypes.DATA; } } return newAttributes as LensDocShape715; diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts index 8264ed4853d3..fcb1e2a5722f 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts @@ -26,7 +26,8 @@ import { XYVisStatePre850, VisState850, } from './types'; -import { layerTypes, LegacyMetricState } from '../../common'; +import { LayerTypes } from '@kbn/expression-xy-plugin/common'; +import { LegacyMetricState } from '../../common'; import { Filter } from '@kbn/es-query'; import { DataViewSpec } from '@kbn/data-views-plugin/common'; @@ -1064,7 +1065,7 @@ describe('Lens migrations', () => { const state = (result.attributes as LensDocShape715).state.visualization; if ('layers' in state) { for (const layer of state.layers) { - expect(layer.layerType).toEqual(layerTypes.DATA); + expect(layer.layerType).toEqual(LayerTypes.DATA); } } }); @@ -1092,7 +1093,7 @@ describe('Lens migrations', () => { const state = (result.attributes as LensDocShape715).state.visualization; if ('layers' in state) { for (const layer of state.layers) { - expect(layer.layerType).toEqual(layerTypes.DATA); + expect(layer.layerType).toEqual(LayerTypes.DATA); } } }); @@ -1109,7 +1110,7 @@ describe('Lens migrations', () => { const state = (result.attributes as LensDocShape715).state.visualization; expect('layerType' in state).toEqual(true); if ('layerType' in state) { - expect(state.layerType).toEqual(layerTypes.DATA); + expect(state.layerType).toEqual(LayerTypes.DATA); } }); it('should add layer info to a datatable visualization', () => { @@ -1125,7 +1126,7 @@ describe('Lens migrations', () => { const state = (result.attributes as LensDocShape715).state.visualization; expect('layerType' in state).toEqual(true); if ('layerType' in state) { - expect(state.layerType).toEqual(layerTypes.DATA); + expect(state.layerType).toEqual(LayerTypes.DATA); } }); it('should add layer info to a heatmap visualization', () => { @@ -1141,7 +1142,7 @@ describe('Lens migrations', () => { const state = (result.attributes as LensDocShape715).state.visualization; expect('layerType' in state).toEqual(true); if ('layerType' in state) { - expect(state.layerType).toEqual(layerTypes.DATA); + expect(state.layerType).toEqual(LayerTypes.DATA); } }); }); diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx index 69467d7ecea8..4a358a5d4386 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx @@ -11,7 +11,6 @@ import styled from 'styled-components'; import { HttpStart } from '@kbn/core/public'; import { addIdToItem } from '@kbn/securitysolution-utils'; import { - CreateExceptionListItemSchema, ExceptionListItemSchema, ExceptionListType, NamespaceType, @@ -24,6 +23,7 @@ import { import { CreateExceptionListItemBuilderSchema, ExceptionsBuilderExceptionItem, + ExceptionsBuilderReturnExceptionItem, OperatorOption, containsValueListEntry, filterExceptionItems, @@ -68,7 +68,7 @@ const initialState: State = { export interface OnChangeProps { errorExists: boolean; - exceptionItems: Array; + exceptionItems: ExceptionsBuilderReturnExceptionItem[]; exceptionsToDelete: ExceptionListItemSchema[]; warningExists: boolean; } @@ -84,15 +84,16 @@ export interface ExceptionBuilderProps { isNestedDisabled: boolean; isOrDisabled: boolean; isOrHidden?: boolean; - listId: string; - listNamespaceType: NamespaceType; + listId: string | undefined; + listNamespaceType: NamespaceType | undefined; listType: ExceptionListType; listTypeSpecificIndexPatternFilter?: ( pattern: DataViewBase, type: ExceptionListType ) => DataViewBase; onChange: (arg: OnChangeProps) => void; - ruleName: string; + exceptionItemName?: string; + ruleName?: string; isDisabled?: boolean; operatorsList?: OperatorOption[]; } @@ -113,6 +114,7 @@ export const ExceptionBuilderComponent = ({ listTypeSpecificIndexPatternFilter, onChange, ruleName, + exceptionItemName, isDisabled = false, osTypes, operatorsList, @@ -289,10 +291,10 @@ export const ExceptionBuilderComponent = ({ const newException = getNewExceptionItem({ listId, namespaceType: listNamespaceType, - ruleName, + ruleName: exceptionItemName ?? `${ruleName ?? 'Rule'} - Exception item`, }); setUpdateExceptions([...exceptions, { ...newException }]); - }, [setUpdateExceptions, exceptions, listId, listNamespaceType, ruleName]); + }, [listId, listNamespaceType, exceptionItemName, ruleName, setUpdateExceptions, exceptions]); // The builder can have existing exception items, or new exception items that have yet // to be created (and thus lack an id), this was creating some React bugs with relying diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js index 19a7ec294110..9abe2997b475 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js @@ -41,15 +41,18 @@ export class KibanaTilemapSource extends AbstractSource { }, ]; } + isSourceStale(mbSource, sourceData) { if (!sourceData.url) { return false; } return mbSource.tiles?.[0] !== sourceData.url; } + async canSkipSourceUpdate() { return false; } + async getUrlTemplate() { const tilemap = getKibanaTileMap(); if (!tilemap.url) { diff --git a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js index a1c1c60d7556..3d682a504c2d 100644 --- a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js +++ b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js @@ -27,15 +27,18 @@ export class WMSSource extends AbstractSource { styles, }; } + isSourceStale(mbSource, sourceData) { if (!sourceData.url) { return false; } return mbSource.tiles?.[0] !== sourceData.url; } + async canSkipSourceUpdate() { return false; } + async getImmutableProperties() { return [ { label: getDataSourceLabel(), value: sourceTitle }, diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts index e894d0f049b2..beb0d5153d89 100644 --- a/x-pack/plugins/maps/public/index.ts +++ b/x-pack/plugins/maps/public/index.ts @@ -35,6 +35,7 @@ export type { MapEmbeddable, MapEmbeddableInput, MapEmbeddableOutput } from './e export type { EMSTermJoinConfig, SampleValuesConfig } from './ems_autosuggest'; export type { ITMSSource } from './classes/sources/tms_source'; +export type { IRasterSource } from './classes/sources/raster_source'; export type { GetFeatureActionsArgs, diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js index d406784344f6..a4f8ab231d08 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js @@ -12,7 +12,6 @@ import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; -import { get, pick } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -26,265 +25,12 @@ import { EuiSpacer, EuiTabbedContent, EuiText, - EuiToolTip, } from '@elastic/eui'; -import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils'; -import { EntityCell } from '../entity_cell'; -import { - getMultiBucketImpactLabel, - getSeverity, - showActualForFunction, - showTypicalForFunction, -} from '../../../../common/util/anomaly_utils'; -import { MULTI_BUCKET_IMPACT } from '../../../../common/constants/multi_bucket_impact'; -import { formatValue } from '../../formatters/format_value'; +import { getSeverity } from '../../../../common/util/anomaly_utils'; import { MAX_CHARS } from './anomalies_table_constants'; -import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; - -const TIME_FIELD_NAME = 'timestamp'; - -function getFilterEntity(entityName, entityValue, filter) { - return ; -} - -function getDetailsItems(anomaly, filter) { - const source = anomaly.source; - - // TODO - when multivariate analyses are more common, - // look in each cause for a 'correlatedByFieldValue' field, - let causes = []; - const sourceCauses = source.causes || []; - let singleCauseByFieldName = undefined; - let singleCauseByFieldValue = undefined; - if (sourceCauses.length === 1) { - // Metrics and probability will already have been placed at the top level. - // If cause has byFieldValue, move it to a top level fields for display. - if (sourceCauses[0].by_field_name !== undefined) { - singleCauseByFieldName = sourceCauses[0].by_field_name; - singleCauseByFieldValue = sourceCauses[0].by_field_value; - } - } else { - causes = sourceCauses.map((cause) => { - const simplified = pick(cause, 'typical', 'actual', 'probability'); - // Get the 'entity field name/value' to display in the cause - - // For by and over, use by_field_name/value (over_field_name/value are in the top level fields) - // For just an 'over' field - the over_field_name/value appear in both top level and cause. - simplified.entityName = cause.by_field_name ? cause.by_field_name : cause.over_field_name; - simplified.entityValue = cause.by_field_value ? cause.by_field_value : cause.over_field_value; - return simplified; - }); - } - - const items = []; - if (source.partition_field_value !== undefined) { - items.push({ - title: source.partition_field_name, - description: getFilterEntity( - source.partition_field_name, - source.partition_field_value, - filter - ), - }); - } - - if (source.by_field_value !== undefined) { - items.push({ - title: source.by_field_name, - description: getFilterEntity(source.by_field_name, source.by_field_value, filter), - }); - } - - if (singleCauseByFieldName !== undefined) { - // Display byField of single cause. - items.push({ - title: singleCauseByFieldName, - description: getFilterEntity(singleCauseByFieldName, singleCauseByFieldValue, filter), - }); - } - - if (source.over_field_value !== undefined) { - items.push({ - title: source.over_field_name, - description: getFilterEntity(source.over_field_name, source.over_field_value, filter), - }); - } - - const anomalyTime = source[TIME_FIELD_NAME]; - let timeDesc = `${formatHumanReadableDateTimeSeconds(anomalyTime)}`; - if (source.bucket_span !== undefined) { - const anomalyEndTime = anomalyTime + source.bucket_span * 1000; - timeDesc = i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.anomalyTimeRangeLabel', { - defaultMessage: '{anomalyTime} to {anomalyEndTime}', - values: { - anomalyTime: formatHumanReadableDateTimeSeconds(anomalyTime), - anomalyEndTime: formatHumanReadableDateTimeSeconds(anomalyEndTime), - }, - }); - } - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.timeTitle', { - defaultMessage: 'Time', - }), - description: timeDesc, - }); - - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.functionTitle', { - defaultMessage: 'Function', - }), - description: - source.function !== ML_JOB_AGGREGATION.METRIC ? source.function : source.function_description, - }); - - if (source.field_name !== undefined) { - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.fieldNameTitle', { - defaultMessage: 'Field name', - }), - description: source.field_name, - }); - } - - const functionDescription = source.function_description || ''; - if (anomaly.actual !== undefined && showActualForFunction(functionDescription) === true) { - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.actualTitle', { - defaultMessage: 'Actual', - }), - description: formatValue(anomaly.actual, source.function, undefined, source), - }); - } - - if (anomaly.typical !== undefined && showTypicalForFunction(functionDescription) === true) { - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.typicalTitle', { - defaultMessage: 'Typical', - }), - description: formatValue(anomaly.typical, source.function, undefined, source), - }); - } - - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.jobIdTitle', { - defaultMessage: 'Job ID', - }), - description: anomaly.jobId, - }); - if ( - source.multi_bucket_impact !== undefined && - source.multi_bucket_impact >= MULTI_BUCKET_IMPACT.LOW - ) { - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.multiBucketImpactTitle', { - defaultMessage: 'Multi-bucket impact', - }), - description: getMultiBucketImpactLabel(source.multi_bucket_impact), - }); - } - - items.push({ - title: ( - - - {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.recordScoreTitle', { - defaultMessage: 'Record score', - })} - - - - ), - description: Math.floor(1000 * source.record_score) / 1000, - }); - - items.push({ - title: ( - - - {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.initialRecordScoreTitle', { - defaultMessage: 'Initial record score', - })} - - - - ), - description: Math.floor(1000 * source.initial_record_score) / 1000, - }); - - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.probabilityTitle', { - defaultMessage: 'Probability', - }), - description: - source.probability !== undefined ? Number.parseFloat(source.probability).toPrecision(3) : '', - }); - - // If there was only one cause, the actual, typical and by_field - // will already have been added for display. - if (causes.length > 1) { - causes.forEach((cause, index) => { - const title = - index === 0 - ? i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.causeValuesTitle', { - defaultMessage: '{causeEntityName} values', - values: { - causeEntityName: cause.entityName, - }, - }) - : ''; - const description = i18n.translate( - 'xpack.ml.anomaliesTable.anomalyDetails.causeValuesDescription', - { - defaultMessage: - '{causeEntityValue} (actual {actualValue}, ' + - 'typical {typicalValue}, probability {probabilityValue})', - values: { - causeEntityValue: cause.entityValue, - actualValue: formatValue(cause.actual, source.function), - typicalValue: formatValue(cause.typical, source.function), - probabilityValue: cause.probability, - }, - } - ); - items.push({ title, description }); - }); - } - - return items; -} -// anomalyInfluencers: [ {fieldName: fieldValue}, {fieldName: fieldValue}, ... ] -function getInfluencersItems(anomalyInfluencers, influencerFilter, numToDisplay) { - const items = []; - - for (let i = 0; i < numToDisplay; i++) { - Object.keys(anomalyInfluencers[i]).forEach((influencerFieldName) => { - const value = anomalyInfluencers[i][influencerFieldName]; - - items.push({ - title: influencerFieldName, - description: getFilterEntity(influencerFieldName, value, influencerFilter), - }); - }); - } - - return items; -} +import { getDetailsItems, getInfluencersItems } from './anomaly_details_utils'; export class AnomalyDetails extends Component { static propTypes = { @@ -518,7 +264,7 @@ export class AnomalyDetails extends Component { renderDetails() { const detailItems = getDetailsItems(this.props.anomaly, this.props.filter); - const isInterimResult = get(this.props.anomaly, 'source.is_interim', false); + const isInterimResult = this.props.anomaly.source?.is_interim ?? false; return ( diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx new file mode 100644 index 000000000000..89eedee36f34 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx @@ -0,0 +1,280 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiToolTip, EuiIcon } from '@elastic/eui'; +import { EntityCell, EntityCellFilter } from '../entity_cell'; +import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils'; +import { + getMultiBucketImpactLabel, + showActualForFunction, + showTypicalForFunction, +} from '../../../../common/util/anomaly_utils'; +import { MULTI_BUCKET_IMPACT } from '../../../../common/constants/multi_bucket_impact'; +import { AnomaliesTableRecord } from '../../../../common/types/anomalies'; +import { formatValue } from '../../formatters/format_value'; +import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; + +const TIME_FIELD_NAME = 'timestamp'; + +interface Cause { + typical: number[]; + actual: number[]; + probability: number; + entityName?: string; + entityValue?: string; +} + +function getFilterEntity(entityName: string, entityValue: string, filter: EntityCellFilter) { + return ; +} + +export function getInfluencersItems( + anomalyInfluencers: Array>, + influencerFilter: EntityCellFilter, + numToDisplay: number +) { + const items: Array<{ title: string; description: React.ReactElement }> = []; + for (let i = 0; i < numToDisplay; i++) { + Object.keys(anomalyInfluencers[i]).forEach((influencerFieldName) => { + const value = anomalyInfluencers[i][influencerFieldName]; + + items.push({ + title: influencerFieldName, + description: getFilterEntity(influencerFieldName, value, influencerFilter), + }); + }); + } + + return items; +} + +export function getDetailsItems(anomaly: AnomaliesTableRecord, filter: EntityCellFilter) { + const source = anomaly.source; + + // TODO - when multivariate analyses are more common, + // look in each cause for a 'correlatedByFieldValue' field, + let causes: Cause[] = []; + const sourceCauses = source.causes || []; + let singleCauseByFieldName; + let singleCauseByFieldValue; + if (sourceCauses.length === 1) { + // Metrics and probability will already have been placed at the top level. + // If cause has byFieldValue, move it to a top level fields for display. + if (sourceCauses[0].by_field_name !== undefined) { + singleCauseByFieldName = sourceCauses[0].by_field_name; + singleCauseByFieldValue = sourceCauses[0].by_field_value; + } + } else { + causes = sourceCauses.map((cause) => { + return { + typical: cause.typical, + actual: cause.actual, + probability: cause.probability, + // // Get the 'entity field name/value' to display in the cause - + // // For by and over, use by_field_name/value (over_field_name/value are in the top level fields) + // // For just an 'over' field - the over_field_name/value appear in both top level and cause. + entityName: cause.by_field_name ? cause.by_field_name : cause.over_field_name, + entityValue: cause.by_field_value ? cause.by_field_value : cause.over_field_value, + }; + }); + } + + const items = []; + if (source.partition_field_value !== undefined && source.partition_field_name !== undefined) { + items.push({ + title: source.partition_field_name, + description: getFilterEntity( + source.partition_field_name, + String(source.partition_field_value), + filter + ), + }); + } + + if (source.by_field_value !== undefined && source.by_field_name !== undefined) { + items.push({ + title: source.by_field_name, + description: getFilterEntity(source.by_field_name, source.by_field_value, filter), + }); + } + + if (singleCauseByFieldName !== undefined && singleCauseByFieldValue !== undefined) { + // Display byField of single cause. + items.push({ + title: singleCauseByFieldName, + description: getFilterEntity(singleCauseByFieldName, singleCauseByFieldValue, filter), + }); + } + + if (source.over_field_value !== undefined && source.over_field_name !== undefined) { + items.push({ + title: source.over_field_name, + description: getFilterEntity(source.over_field_name, source.over_field_value, filter), + }); + } + + const anomalyTime = source[TIME_FIELD_NAME]; + let timeDesc = `${formatHumanReadableDateTimeSeconds(anomalyTime)}`; + if (source.bucket_span !== undefined) { + const anomalyEndTime = anomalyTime + source.bucket_span * 1000; + timeDesc = i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.anomalyTimeRangeLabel', { + defaultMessage: '{anomalyTime} to {anomalyEndTime}', + values: { + anomalyTime: formatHumanReadableDateTimeSeconds(anomalyTime), + anomalyEndTime: formatHumanReadableDateTimeSeconds(anomalyEndTime), + }, + }); + } + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.timeTitle', { + defaultMessage: 'Time', + }), + description: timeDesc, + }); + + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.functionTitle', { + defaultMessage: 'Function', + }), + description: + source.function !== ML_JOB_AGGREGATION.METRIC ? source.function : source.function_description, + }); + + if (source.field_name !== undefined) { + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.fieldNameTitle', { + defaultMessage: 'Field name', + }), + description: source.field_name, + }); + } + + const functionDescription = source.function_description || ''; + if (anomaly.actual !== undefined && showActualForFunction(functionDescription) === true) { + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.actualTitle', { + defaultMessage: 'Actual', + }), + description: formatValue(anomaly.actual, source.function, undefined, source), + }); + } + + if (anomaly.typical !== undefined && showTypicalForFunction(functionDescription) === true) { + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.typicalTitle', { + defaultMessage: 'Typical', + }), + description: formatValue(anomaly.typical, source.function, undefined, source), + }); + } + + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.jobIdTitle', { + defaultMessage: 'Job ID', + }), + description: anomaly.jobId, + }); + + if ( + source.multi_bucket_impact !== undefined && + source.multi_bucket_impact >= MULTI_BUCKET_IMPACT.LOW + ) { + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.multiBucketImpactTitle', { + defaultMessage: 'Multi-bucket impact', + }), + description: getMultiBucketImpactLabel(source.multi_bucket_impact), + }); + } + + items.push({ + title: ( + + + {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.recordScoreTitle', { + defaultMessage: 'Record score', + })} + + + + ), + description: Math.floor(1000 * source.record_score) / 1000, + }); + + items.push({ + title: ( + + + {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.initialRecordScoreTitle', { + defaultMessage: 'Initial record score', + })} + + + + ), + description: Math.floor(1000 * source.initial_record_score) / 1000, + }); + + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.probabilityTitle', { + defaultMessage: 'Probability', + }), + description: + // @ts-expect-error parseFloat accept take a number + source.probability !== undefined ? Number.parseFloat(source.probability).toPrecision(3) : '', + }); + + // If there was only one cause, the actual, typical and by_field + // will already have been added for display. + if (causes.length > 1) { + causes.forEach((cause, index) => { + const title = + index === 0 + ? i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.causeValuesTitle', { + defaultMessage: '{causeEntityName} values', + values: { + causeEntityName: cause.entityName, + }, + }) + : ''; + const description = i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.causeValuesDescription', + { + defaultMessage: + '{causeEntityValue} (actual {actualValue}, ' + + 'typical {typicalValue}, probability {probabilityValue})', + values: { + causeEntityValue: cause.entityValue, + actualValue: formatValue(cause.actual, source.function), + typicalValue: formatValue(cause.typical, source.function), + probabilityValue: cause.probability, + }, + } + ); + items.push({ title, description }); + }); + } + + return items; +} diff --git a/x-pack/plugins/monitoring/server/config.test.ts b/x-pack/plugins/monitoring/server/config.test.ts index 43410019b6b8..98aa22f2134f 100644 --- a/x-pack/plugins/monitoring/server/config.test.ts +++ b/x-pack/plugins/monitoring/server/config.test.ts @@ -60,9 +60,11 @@ describe('config schema', () => { "healthCheck": Object { "delay": "PT2.5S", }, + "idleSocketTimeout": "PT1M", "ignoreVersionMismatch": false, "logFetchCount": 10, "logQueries": false, + "maxIdleSockets": 256, "maxSockets": Infinity, "pingTimeout": "PT30S", "requestHeadersWhitelist": Array [ diff --git a/x-pack/plugins/observability/public/config/alert_feature_ids.ts b/x-pack/plugins/observability/public/config/alert_feature_ids.ts new file mode 100644 index 000000000000..f0900c8aa408 --- /dev/null +++ b/x-pack/plugins/observability/public/config/alert_feature_ids.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AlertConsumers } from '@kbn/rule-data-utils'; +import type { ValidFeatureId } from '@kbn/rule-data-utils'; + +export const observabilityAlertFeatureIds: ValidFeatureId[] = [ + AlertConsumers.APM, + AlertConsumers.INFRASTRUCTURE, + AlertConsumers.LOGS, + AlertConsumers.UPTIME, +]; diff --git a/x-pack/plugins/observability/public/config/index.ts b/x-pack/plugins/observability/public/config/index.ts index 34d783180750..f9f975c4c824 100644 --- a/x-pack/plugins/observability/public/config/index.ts +++ b/x-pack/plugins/observability/public/config/index.ts @@ -7,6 +7,7 @@ export { paths } from './paths'; export { translations } from './translations'; +export { observabilityAlertFeatureIds } from './alert_feature_ids'; export enum AlertingPages { alerts = 'alerts', diff --git a/x-pack/plugins/observability/public/hooks/use_alert_data_view.test.ts b/x-pack/plugins/observability/public/hooks/use_alert_data_view.test.ts new file mode 100644 index 000000000000..77db2492d422 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_alert_data_view.test.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataView } from '@kbn/data-views-plugin/common'; +import type { ValidFeatureId } from '@kbn/rule-data-utils'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { AsyncState } from 'react-use/lib/useAsync'; +import { kibanaStartMock } from '../utils/kibana_react.mock'; +import { observabilityAlertFeatureIds } from '../config'; +import { useAlertDataView } from './use_alert_data_view'; + +const mockUseKibanaReturnValue = kibanaStartMock.startContract(); + +jest.mock('@kbn/kibana-react-plugin/public', () => ({ + __esModule: true, + useKibana: jest.fn(() => mockUseKibanaReturnValue), +})); + +describe('useAlertDataView', () => { + const mockedDataView = 'dataView'; + + beforeEach(() => { + mockUseKibanaReturnValue.services.http.get.mockImplementation(async () => ({ + index_name: [ + '.alerts-observability.uptime.alerts-*', + '.alerts-observability.metrics.alerts-*', + '.alerts-observability.logs.alerts-*', + '.alerts-observability.apm.alerts-*', + ], + })); + mockUseKibanaReturnValue.services.data.dataViews.create.mockImplementation( + async () => mockedDataView + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('initially is loading and does not have data', async () => { + await act(async () => { + const mockedAsyncDataView = { + loading: true, + }; + + const { result, waitForNextUpdate } = renderHook>(() => + useAlertDataView(observabilityAlertFeatureIds) + ); + + await waitForNextUpdate(); + + expect(result.current).toEqual(mockedAsyncDataView); + }); + }); + + it('returns dataView for the provided featureIds', async () => { + await act(async () => { + const mockedAsyncDataView = { + loading: false, + value: mockedDataView, + }; + + const { result, waitForNextUpdate } = renderHook>(() => + useAlertDataView(observabilityAlertFeatureIds) + ); + + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual(mockedAsyncDataView); + }); + }); + + it('returns error with no data when error happens', async () => { + const error = new Error('http error'); + mockUseKibanaReturnValue.services.http.get.mockImplementation(async () => { + throw error; + }); + + await act(async () => { + const mockedAsyncDataView = { + loading: false, + error, + }; + + const { result, waitForNextUpdate } = renderHook>(() => + useAlertDataView(observabilityAlertFeatureIds) + ); + + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual(mockedAsyncDataView); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/hooks/use_alert_data_view.ts b/x-pack/plugins/observability/public/hooks/use_alert_data_view.ts new file mode 100644 index 000000000000..aad2a8e030eb --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_alert_data_view.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 { DataView } from '@kbn/data-views-plugin/common'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common'; +import type { ValidFeatureId } from '@kbn/rule-data-utils'; +import useAsync from 'react-use/lib/useAsync'; +import type { AsyncState } from 'react-use/lib/useAsync'; + +import { ObservabilityAppServices } from '../application/types'; + +export function useAlertDataView(featureIds: ValidFeatureId[]): AsyncState { + const { http, data: dataService } = useKibana().services; + const features = featureIds.sort().join(','); + + const dataView = useAsync(async () => { + const { index_name: indexNames } = await http.get<{ index_name: string[] }>( + `${BASE_RAC_ALERTS_API_PATH}/index`, + { + query: { features }, + } + ); + + return dataService.dataViews.create({ title: indexNames.join(',') }); + }, [features]); + + return dataView; +} diff --git a/x-pack/plugins/observability/public/hooks/use_alert_index_names.ts b/x-pack/plugins/observability/public/hooks/use_alert_index_names.ts deleted file mode 100644 index 09ef1b8debc4..000000000000 --- a/x-pack/plugins/observability/public/hooks/use_alert_index_names.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useFetcher } from './use_fetcher'; -import { callObservabilityApi } from '../services/call_observability_api'; - -const NO_INDEX_NAMES: string[] = []; - -export function useAlertIndexNames() { - const { data: indexNames = NO_INDEX_NAMES } = useFetcher(({ signal }) => { - return callObservabilityApi('GET /api/observability/rules/alerts/dynamic_index_pattern', { - signal, - params: { - query: { - namespace: 'default', - registrationContexts: [ - 'observability.apm', - 'observability.logs', - 'observability.metrics', - 'observability.uptime', - ], - }, - }, - }); - }, []); - - return indexNames; -} diff --git a/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts b/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts index 5d87ff009040..fe79b0650d52 100644 --- a/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts +++ b/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts @@ -48,13 +48,23 @@ const triggersActionsUiStartMock = { }, }; +const data = { + createStart() { + return { + dataViews: { + create: jest.fn(), + }, + }; + }, +}; + export const observabilityPublicPluginsStartMock = { createStart() { return { cases: mockCasesContract(), embeddable: embeddableStartMock.createStart(), triggersActionsUi: triggersActionsUiStartMock.createStart(), - data: null, + data: data.createStart(), lens: null, discover: null, }; diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx index 479e0fc1b7e0..2cc05ba47ec0 100644 --- a/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx @@ -5,24 +5,24 @@ * 2.0. */ -import { DataViewBase } from '@kbn/es-query'; import React, { useMemo, useState } from 'react'; import { TimeHistory } from '@kbn/data-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/public'; import { SearchBar } from '@kbn/unified-search-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { ValidFeatureId } from '@kbn/rule-data-utils'; import { translations } from '../../../config'; +import { useAlertDataView } from '../../../hooks/use_alert_data_view'; type QueryLanguageType = 'lucene' | 'kuery'; export function AlertsSearchBar({ - dynamicIndexPatterns, + featureIds, onQueryChange, query, rangeFrom, rangeTo, }: { - dynamicIndexPatterns: DataViewBase[]; + featureIds: ValidFeatureId[]; rangeFrom?: string; rangeTo?: string; query?: string; @@ -35,20 +35,11 @@ export function AlertsSearchBar({ return new TimeHistory(new Storage(localStorage)); }, []); const [queryLanguage, setQueryLanguage] = useState('kuery'); - - const compatibleIndexPatterns = useMemo( - () => - dynamicIndexPatterns.map((dynamicIndexPattern) => ({ - title: dynamicIndexPattern.title ?? '', - id: dynamicIndexPattern.id ?? '', - fields: dynamicIndexPattern.fields, - })), - [dynamicIndexPatterns] - ); + const { value: dataView, loading, error } = useAlertDataView(featureIds); return ( => { - if (indexNames.length === 0) { - return []; - } - - return [ - { - id: 'dynamic-observability-alerts-table-index-pattern', - title: indexNames.join(','), - fields: await dataViews.getFieldsForWildcard({ - pattern: indexNames.join(','), - allowNoIndex: true, - }), - }, - ]; - }, [indexNames]); - const onRefresh = () => { setRefreshNow(new Date().getTime()); }; @@ -227,7 +205,7 @@ function AlertsPage() { trace.FrameIDs.length)); +const tree = createCalleeTree(events, stackTraces, stackFrames, executables, totalFrames); + describe('Callee operations', () => { - test('1', () => { - const totalSamples = sum([...events.values()]); - const totalFrames = sum([...stackTraces.values()].map((trace) => trace.FrameIDs.length)); + test('inclusive count of root equals total sampled stacktraces', () => { + expect(tree.CountInclusive[0]).toEqual(totalSamples); + }); - const tree = createCalleeTree(events, stackTraces, stackFrames, executables, totalFrames); + test('inclusive count for each node should be greater than or equal to its children', () => { + const allGreaterThanOrEqual = tree.Edges.map( + (children, i) => + tree.CountInclusive[i] >= sum([...children.values()].map((j) => tree.CountInclusive[j])) + ); + expect(allGreaterThanOrEqual).toBeTruthy(); + }); - expect(tree.Samples[0]).toEqual(totalSamples); - expect(tree.CountInclusive[0]).toEqual(totalSamples); + test('exclusive count of root is zero', () => { expect(tree.CountExclusive[0]).toEqual(0); }); + + test('tree de-duplicates sibling nodes', () => { + expect(tree.Size).toEqual(totalFrames - 2); + }); }); diff --git a/x-pack/plugins/profiling/common/callee.ts b/x-pack/plugins/profiling/common/callee.ts index 63db0640513c..5c347f034760 100644 --- a/x-pack/plugins/profiling/common/callee.ts +++ b/x-pack/plugins/profiling/common/callee.ts @@ -5,20 +5,15 @@ * 2.0. */ -import fnv from 'fnv-plus'; - import { createFrameGroupID, FrameGroupID } from './frame_group'; import { - createStackFrameMetadata, emptyExecutable, emptyStackFrame, emptyStackTrace, Executable, FileID, - getCalleeLabel, StackFrame, StackFrameID, - StackFrameMetadata, StackTrace, StackTraceID, } from './profiling'; @@ -29,93 +24,56 @@ export interface CalleeTree { Size: number; Edges: Array>; - ID: string[]; + FileID: string[]; FrameType: number[]; - FrameID: StackFrameID[]; - FileID: FileID[]; - Label: string[]; + ExeFilename: string[]; + AddressOrLine: number[]; + FunctionName: string[]; + FunctionOffset: number[]; + SourceFilename: string[]; + SourceLine: number[]; - Samples: number[]; CountInclusive: number[]; CountExclusive: number[]; } -function initCalleeTree(capacity: number): CalleeTree { - const metadata = createStackFrameMetadata(); - const frameGroupID = createFrameGroupID( - metadata.FileID, - metadata.AddressOrLine, - metadata.ExeFileName, - metadata.SourceFilename, - metadata.FunctionName - ); +export function createCalleeTree( + events: Map, + stackTraces: Map, + stackFrames: Map, + executables: Map, + totalFrames: number +): CalleeTree { const tree: CalleeTree = { Size: 1, - Edges: new Array(capacity), - ID: new Array(capacity), - FrameType: new Array(capacity), - FrameID: new Array(capacity), - FileID: new Array(capacity), - Label: new Array(capacity), - Samples: new Array(capacity), - CountInclusive: new Array(capacity), - CountExclusive: new Array(capacity), + Edges: new Array(totalFrames), + FileID: new Array(totalFrames), + FrameType: new Array(totalFrames), + ExeFilename: new Array(totalFrames), + AddressOrLine: new Array(totalFrames), + FunctionName: new Array(totalFrames), + FunctionOffset: new Array(totalFrames), + SourceFilename: new Array(totalFrames), + SourceLine: new Array(totalFrames), + + CountInclusive: new Array(totalFrames), + CountExclusive: new Array(totalFrames), }; tree.Edges[0] = new Map(); - tree.ID[0] = fnv.fast1a64utf(frameGroupID).toString(); - tree.FrameType[0] = metadata.FrameType; - tree.FrameID[0] = metadata.FrameID; - tree.FileID[0] = metadata.FileID; - tree.Label[0] = 'root: Represents 100% of CPU time.'; - tree.Samples[0] = 0; + tree.FileID[0] = ''; + tree.FrameType[0] = 0; + tree.ExeFilename[0] = ''; + tree.AddressOrLine[0] = 0; + tree.FunctionName[0] = ''; + tree.FunctionOffset[0] = 0; + tree.SourceFilename[0] = ''; + tree.SourceLine[0] = 0; + tree.CountInclusive[0] = 0; tree.CountExclusive[0] = 0; - return tree; -} - -function insertNode( - tree: CalleeTree, - parent: NodeID, - metadata: StackFrameMetadata, - frameGroupID: FrameGroupID, - samples: number -) { - const node = tree.Size; - - tree.Edges[parent].set(frameGroupID, node); - tree.Edges[node] = new Map(); - - tree.ID[node] = fnv.fast1a64utf(`${tree.ID[parent]}${frameGroupID}`).toString(); - tree.FrameType[node] = metadata.FrameType; - tree.FrameID[node] = metadata.FrameID; - tree.FileID[node] = metadata.FileID; - tree.Label[node] = getCalleeLabel(metadata); - tree.Samples[node] = samples; - tree.CountInclusive[node] = 0; - tree.CountExclusive[node] = 0; - - tree.Size++; - - return node; -} - -// createCalleeTree creates a tree from the trace results, the number of -// times that the trace has been seen, and the respective metadata. -// -// The resulting data structure contains all of the data, but is not yet in the -// form most easily digestible by others. -export function createCalleeTree( - events: Map, - stackTraces: Map, - stackFrames: Map, - executables: Map, - totalFrames: number -): CalleeTree { - const tree = initCalleeTree(totalFrames); - const sortedStackTraceIDs = new Array(); for (const trace of stackTraces.keys()) { sortedStackTraceIDs.push(trace); @@ -139,7 +97,9 @@ export function createCalleeTree( const samples = events.get(stackTraceID) ?? 0; let currentNode = 0; - tree.Samples[currentNode] += samples; + + tree.CountInclusive[currentNode] += samples; + tree.CountExclusive[currentNode] = 0; for (let i = 0; i < lenStackTrace; i++) { const frameID = stackTrace.FrameIDs[i]; @@ -159,25 +119,27 @@ export function createCalleeTree( let node = tree.Edges[currentNode].get(frameGroupID); if (node === undefined) { - const metadata = createStackFrameMetadata({ - FrameID: frameID, - FileID: fileID, - AddressOrLine: addressOrLine, - FrameType: stackTrace.Types[i], - FunctionName: frame.FunctionName, - FunctionOffset: frame.FunctionOffset, - SourceLine: frame.LineNumber, - SourceFilename: frame.FileName, - ExeFileName: executable.FileName, - }); - - node = insertNode(tree, currentNode, metadata, frameGroupID, samples); + node = tree.Size; + + tree.FileID[node] = fileID; + tree.FrameType[node] = stackTrace.Types[i]; + tree.ExeFilename[node] = executable.FileName; + tree.AddressOrLine[node] = addressOrLine; + tree.FunctionName[node] = frame.FunctionName; + tree.FunctionOffset[node] = frame.FunctionOffset; + tree.SourceLine[node] = frame.LineNumber; + tree.SourceFilename[node] = frame.FileName; + tree.CountInclusive[node] = samples; + tree.CountExclusive[node] = 0; + + tree.Edges[currentNode].set(frameGroupID, node); + tree.Edges[node] = new Map(); + + tree.Size++; } else { - tree.Samples[node] += samples; + tree.CountInclusive[node] += samples; } - tree.CountInclusive[node] += samples; - if (i === lenStackTrace - 1) { // Leaf frame: sum up counts for exclusive CPU. tree.CountExclusive[node] += samples; @@ -186,8 +148,5 @@ export function createCalleeTree( } } - tree.CountExclusive[0] = 0; - tree.CountInclusive[0] = tree.Samples[0]; - return tree; } diff --git a/x-pack/plugins/profiling/common/columnar_view_model.test.ts b/x-pack/plugins/profiling/common/columnar_view_model.test.ts new file mode 100644 index 000000000000..c41e2b0aef4e --- /dev/null +++ b/x-pack/plugins/profiling/common/columnar_view_model.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sum } from 'lodash'; +import { createCalleeTree } from './callee'; +import { createColumnarViewModel } from './columnar_view_model'; +import { createBaseFlameGraph, createFlameGraph } from './flamegraph'; + +import { events, stackTraces, stackFrames, executables } from './__fixtures__/stacktraces'; + +const totalFrames = sum([...stackTraces.values()].map((trace) => trace.FrameIDs.length)); + +const tree = createCalleeTree(events, stackTraces, stackFrames, executables, totalFrames); +const graph = createFlameGraph(createBaseFlameGraph(tree, 60)); + +describe('Columnar view model operations', () => { + test('color values are generated by default', () => { + const viewModel = createColumnarViewModel(graph); + + expect(sum(viewModel.color)).toBeGreaterThan(0); + }); + + test('color values are not generated when disabled', () => { + const viewModel = createColumnarViewModel(graph, false); + + expect(sum(viewModel.color)).toEqual(0); + }); +}); diff --git a/x-pack/plugins/profiling/common/columnar_view_model.ts b/x-pack/plugins/profiling/common/columnar_view_model.ts new file mode 100644 index 000000000000..21e520b1f994 --- /dev/null +++ b/x-pack/plugins/profiling/common/columnar_view_model.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 { ColumnarViewModel } from '@elastic/charts'; + +import { ElasticFlameGraph } from './flamegraph'; + +/* + * Helper to calculate the color of a given block to be drawn. The desirable outcomes of this are: + * Each of the following frame types should get a different set of color hues: + * + * 0 = Unsymbolized frame + * 1 = Python + * 2 = PHP + * 3 = Native + * 4 = Kernel + * 5 = JVM/Hotspot + * 6 = Ruby + * 7 = Perl + * 8 = JavaScript + * + * This is most easily achieved by mapping frame types to different color variations, using + * the x-position we can use different colors for adjacent blocks while keeping a similar hue + * + * Taken originally from prodfiler_ui/src/helpers/Pixi/frameTypeToColors.tsx + */ +const frameTypeToColors = [ + [0xfd8484, 0xfd9d9d, 0xfeb5b5, 0xfecece], + [0xfcae6b, 0xfdbe89, 0xfdcea6, 0xfedfc4], + [0xfcdb82, 0xfde29b, 0xfde9b4, 0xfef1cd], + [0x6dd0dc, 0x8ad9e3, 0xa7e3ea, 0xc5ecf1], + [0x7c9eff, 0x96b1ff, 0xb0c5ff, 0xcbd8ff], + [0x65d3ac, 0x84dcbd, 0xa3e5cd, 0xc1edde], + [0xd79ffc, 0xdfb2fd, 0xe7c5fd, 0xefd9fe], + [0xf98bb9, 0xfaa2c7, 0xfbb9d5, 0xfdd1e3], + [0xcbc3e3, 0xd5cfe8, 0xdfdbee, 0xeae7f3], +]; + +function frameTypeToRGB(frameType: number, x: number): number { + return frameTypeToColors[frameType][x % 4]; +} + +export function rgbToRGBA(rgb: number): number[] { + return [ + Math.floor(rgb / 65536) / 255, + (Math.floor(rgb / 256) % 256) / 255, + (rgb % 256) / 255, + 1.0, + ]; +} + +function normalize(n: number, lower: number, upper: number): number { + return (n - lower) / (upper - lower); +} + +// createColumnarViewModel normalizes the columnar representation into a form +// consumed by the flamegraph in the UI. +export function createColumnarViewModel( + flamegraph: ElasticFlameGraph, + assignColors: boolean = true +): ColumnarViewModel { + const numNodes = flamegraph.Size; + const xs = new Float32Array(numNodes); + const ys = new Float32Array(numNodes); + + const queue = [{ x: 0, depth: 1, node: 0 }]; + + while (queue.length > 0) { + const { x, depth, node } = queue.pop()!; + + xs[node] = x; + ys[node] = depth; + + // For a deterministic result we have to walk the callees in a deterministic + // order. A deterministic result allows deterministic UI views, something + // that users expect. + const children = flamegraph.Edges[node].sort((n1, n2) => { + if (flamegraph.CountInclusive[n1] > flamegraph.CountInclusive[n2]) { + return -1; + } + if (flamegraph.CountInclusive[n1] < flamegraph.CountInclusive[n2]) { + return 1; + } + return flamegraph.ID[n1].localeCompare(flamegraph.ID[n2]); + }); + + let delta = 0; + for (const child of children) { + delta += flamegraph.CountInclusive[child]; + } + + for (let i = children.length - 1; i >= 0; i--) { + delta -= flamegraph.CountInclusive[children[i]]; + queue.push({ x: x + delta, depth: depth + 1, node: children[i] }); + } + } + + const colors = new Float32Array(numNodes * 4); + + if (assignColors) { + for (let i = 0; i < numNodes; i++) { + const rgba = rgbToRGBA(frameTypeToRGB(flamegraph.FrameType[i], xs[i])); + colors.set(rgba, 4 * i); + } + } + + const position = new Float32Array(numNodes * 2); + const maxX = flamegraph.CountInclusive[0]; + const maxY = ys.reduce((max, n) => (n > max ? n : max), 0); + + for (let i = 0; i < numNodes; i++) { + const j = 2 * i; + position[j] = normalize(xs[i], 0, maxX); + position[j + 1] = normalize(maxY - ys[i], 0, maxY); + } + + const size = new Float32Array(numNodes); + + for (let i = 0; i < numNodes; i++) { + size[i] = normalize(flamegraph.CountInclusive[i], 0, maxX); + } + + return { + label: flamegraph.Label.slice(0, numNodes), + value: Float64Array.from(flamegraph.CountInclusive.slice(0, numNodes)), + color: colors, + position0: position, + position1: position, + size0: size, + size1: size, + } as ColumnarViewModel; +} diff --git a/x-pack/plugins/profiling/common/flamegraph.test.ts b/x-pack/plugins/profiling/common/flamegraph.test.ts index 3852d0152bf1..5f13d8f9db89 100644 --- a/x-pack/plugins/profiling/common/flamegraph.test.ts +++ b/x-pack/plugins/profiling/common/flamegraph.test.ts @@ -7,26 +7,36 @@ import { sum } from 'lodash'; import { createCalleeTree } from './callee'; -import { createColumnarViewModel, createFlameGraph } from './flamegraph'; +import { createBaseFlameGraph, createFlameGraph } from './flamegraph'; import { events, stackTraces, stackFrames, executables } from './__fixtures__/stacktraces'; +const totalFrames = sum([...stackTraces.values()].map((trace) => trace.FrameIDs.length)); +const tree = createCalleeTree(events, stackTraces, stackFrames, executables, totalFrames); +const baseFlamegraph = createBaseFlameGraph(tree, 60); +const flamegraph = createFlameGraph(baseFlamegraph); + describe('Flamegraph operations', () => { - test('1', () => { - const totalSamples = sum([...events.values()]); - const totalFrames = sum([...stackTraces.values()].map((trace) => trace.FrameIDs.length)); + test('base flamegraph has non-zero total seconds', () => { + expect(baseFlamegraph.TotalSeconds).toEqual(60); + }); - const tree = createCalleeTree(events, stackTraces, stackFrames, executables, totalFrames); - const graph = createFlameGraph(tree, 60, totalSamples, totalSamples); + test('base flamegraph has one more node than the number of edges', () => { + const numEdges = baseFlamegraph.Edges.flatMap((edge) => edge).length; - expect(graph.Size).toEqual(totalFrames - 2); + expect(numEdges).toEqual(baseFlamegraph.Size - 1); + }); - const viewModel1 = createColumnarViewModel(graph); + test('all flamegraph IDs are the same non-zero length', () => { + // 16 is the length of a 64-bit FNV-1a hash encoded to a hex string + const allSameLengthIDs = flamegraph.ID.every((id) => id.length === 16); - expect(sum(viewModel1.color)).toBeGreaterThan(0); + expect(allSameLengthIDs).toBeTruthy(); + }); - const viewModel2 = createColumnarViewModel(graph, false); + test('all flamegraph labels are non-empty', () => { + const allNonEmptyLabels = flamegraph.Label.every((id) => id.length > 0); - expect(sum(viewModel2.color)).toEqual(0); + expect(allNonEmptyLabels).toBeTruthy(); }); }); diff --git a/x-pack/plugins/profiling/common/flamegraph.ts b/x-pack/plugins/profiling/common/flamegraph.ts index e392022a18fc..eacd4a34322c 100644 --- a/x-pack/plugins/profiling/common/flamegraph.ts +++ b/x-pack/plugins/profiling/common/flamegraph.ts @@ -5,106 +5,54 @@ * 2.0. */ -import { ColumnarViewModel } from '@elastic/charts'; - import { CalleeTree } from './callee'; +import { createFrameGroupID } from './frame_group'; +import { fnv1a64 } from './hash'; +import { createStackFrameMetadata, getCalleeLabel } from './profiling'; + +export enum FlameGraphComparisonMode { + Absolute = 'absolute', + Relative = 'relative', +} -export interface ElasticFlameGraph { +export interface BaseFlameGraph { Size: number; Edges: number[][]; - ID: string[]; + FileID: string[]; FrameType: number[]; - FrameID: string[]; - ExecutableID: string[]; - Label: string[]; + ExeFilename: string[]; + AddressOrLine: number[]; + FunctionName: string[]; + FunctionOffset: number[]; + SourceFilename: string[]; + SourceLine: number[]; - Samples: number[]; CountInclusive: number[]; CountExclusive: number[]; TotalSeconds: number; - TotalTraces: number; - SampledTraces: number; -} - -export enum FlameGraphComparisonMode { - Absolute = 'absolute', - Relative = 'relative', -} - -/* - * Helper to calculate the color of a given block to be drawn. The desirable outcomes of this are: - * Each of the following frame types should get a different set of color hues: - * - * 0 = Unsymbolized frame - * 1 = Python - * 2 = PHP - * 3 = Native - * 4 = Kernel - * 5 = JVM/Hotspot - * 6 = Ruby - * 7 = Perl - * 8 = JavaScript - * - * This is most easily achieved by mapping frame types to different color variations, using - * the x-position we can use different colors for adjacent blocks while keeping a similar hue - * - * Taken originally from prodfiler_ui/src/helpers/Pixi/frameTypeToColors.tsx - */ -const frameTypeToColors = [ - [0xfd8484, 0xfd9d9d, 0xfeb5b5, 0xfecece], - [0xfcae6b, 0xfdbe89, 0xfdcea6, 0xfedfc4], - [0xfcdb82, 0xfde29b, 0xfde9b4, 0xfef1cd], - [0x6dd0dc, 0x8ad9e3, 0xa7e3ea, 0xc5ecf1], - [0x7c9eff, 0x96b1ff, 0xb0c5ff, 0xcbd8ff], - [0x65d3ac, 0x84dcbd, 0xa3e5cd, 0xc1edde], - [0xd79ffc, 0xdfb2fd, 0xe7c5fd, 0xefd9fe], - [0xf98bb9, 0xfaa2c7, 0xfbb9d5, 0xfdd1e3], - [0xcbc3e3, 0xd5cfe8, 0xdfdbee, 0xeae7f3], -]; - -function frameTypeToRGB(frameType: number, x: number): number { - return frameTypeToColors[frameType][x % 4]; } -export function rgbToRGBA(rgb: number): number[] { - return [ - Math.floor(rgb / 65536) / 255, - (Math.floor(rgb / 256) % 256) / 255, - (rgb % 256) / 255, - 1.0, - ]; -} - -function normalize(n: number, lower: number, upper: number): number { - return (n - lower) / (upper - lower); -} - -// createFlameGraph encapsulates the tree representation into a serialized form. -export function createFlameGraph( - tree: CalleeTree, - totalSeconds: number, - totalTraces: number, - sampledTraces: number -): ElasticFlameGraph { - const graph: ElasticFlameGraph = { +// createBaseFlameGraph encapsulates the tree representation into a serialized form. +export function createBaseFlameGraph(tree: CalleeTree, totalSeconds: number): BaseFlameGraph { + const graph: BaseFlameGraph = { Size: tree.Size, Edges: new Array(tree.Size), - ID: tree.ID.slice(0, tree.Size), - Label: tree.Label.slice(0, tree.Size), - FrameID: tree.FrameID.slice(0, tree.Size), + FileID: tree.FileID.slice(0, tree.Size), FrameType: tree.FrameType.slice(0, tree.Size), - ExecutableID: tree.FileID.slice(0, tree.Size), + ExeFilename: tree.ExeFilename.slice(0, tree.Size), + AddressOrLine: tree.AddressOrLine.slice(0, tree.Size), + FunctionName: tree.FunctionName.slice(0, tree.Size), + FunctionOffset: tree.FunctionOffset.slice(0, tree.Size), + SourceFilename: tree.SourceFilename.slice(0, tree.Size), + SourceLine: tree.SourceLine.slice(0, tree.Size), - Samples: tree.Samples.slice(0, tree.Size), CountInclusive: tree.CountInclusive.slice(0, tree.Size), CountExclusive: tree.CountExclusive.slice(0, tree.Size), TotalSeconds: totalSeconds, - TotalTraces: totalTraces, - SampledTraces: sampledTraces, }; for (let i = 0; i < tree.Size; i++) { @@ -120,80 +68,79 @@ export function createFlameGraph( return graph; } -// createColumnarViewModel normalizes the columnar representation into a form -// consumed by the flamegraph in the UI. -export function createColumnarViewModel( - flamegraph: ElasticFlameGraph, - assignColors: boolean = true -): ColumnarViewModel { - const numNodes = flamegraph.Size; - const xs = new Float32Array(numNodes); - const ys = new Float32Array(numNodes); +export interface ElasticFlameGraph extends BaseFlameGraph { + ID: string[]; + Label: string[]; +} - const queue = [{ x: 0, depth: 1, node: 0 }]; +// createFlameGraph combines the base flamegraph with CPU-intensive values. +// This allows us to create a flamegraph in two steps (e.g. first on the server +// and finally in the browser). +export function createFlameGraph(base: BaseFlameGraph): ElasticFlameGraph { + const graph: ElasticFlameGraph = { + Size: base.Size, + Edges: base.Edges, - while (queue.length > 0) { - const { x, depth, node } = queue.pop()!; - - xs[node] = x; - ys[node] = depth; - - // For a deterministic result we have to walk the callees in a deterministic - // order. A deterministic result allows deterministic UI views, something - // that users expect. - const children = flamegraph.Edges[node].sort((n1, n2) => { - if (flamegraph.Samples[n1] > flamegraph.Samples[n2]) { - return -1; - } - if (flamegraph.Samples[n1] < flamegraph.Samples[n2]) { - return 1; - } - return flamegraph.ID[n1].localeCompare(flamegraph.ID[n2]); - }); + FileID: base.FileID, + FrameType: base.FrameType, + ExeFilename: base.ExeFilename, + AddressOrLine: base.AddressOrLine, + FunctionName: base.FunctionName, + FunctionOffset: base.FunctionOffset, + SourceFilename: base.SourceFilename, + SourceLine: base.SourceLine, - let delta = 0; - for (const child of children) { - delta += flamegraph.Samples[child]; - } + CountInclusive: base.CountInclusive, + CountExclusive: base.CountExclusive, - for (let i = children.length - 1; i >= 0; i--) { - delta -= flamegraph.Samples[children[i]]; - queue.push({ x: x + delta, depth: depth + 1, node: children[i] }); - } - } + ID: new Array(base.Size), + Label: new Array(base.Size), - const colors = new Float32Array(numNodes * 4); + TotalSeconds: base.TotalSeconds, + }; - if (assignColors) { - for (let i = 0; i < numNodes; i++) { - const rgba = rgbToRGBA(frameTypeToRGB(flamegraph.FrameType[i], xs[i])); - colors.set(rgba, 4 * i); - } - } + const rootFrameGroupID = createFrameGroupID( + graph.FileID[0], + graph.AddressOrLine[0], + graph.ExeFilename[0], + graph.SourceFilename[0], + graph.FunctionName[0] + ); - const position = new Float32Array(numNodes * 2); - const maxX = flamegraph.Samples[0]; - const maxY = ys.reduce((max, n) => (n > max ? n : max), 0); + graph.ID[0] = fnv1a64(new TextEncoder().encode(rootFrameGroupID)); - for (let i = 0; i < numNodes; i++) { - const j = 2 * i; - position[j] = normalize(xs[i], 0, maxX); - position[j + 1] = normalize(maxY - ys[i], 0, maxY); + const queue = [0]; + while (queue.length > 0) { + const parent = queue.pop()!; + for (const child of graph.Edges[parent]) { + const frameGroupID = createFrameGroupID( + graph.FileID[child], + graph.AddressOrLine[child], + graph.ExeFilename[child], + graph.SourceFilename[child], + graph.FunctionName[child] + ); + const bytes = new TextEncoder().encode(graph.ID[parent] + frameGroupID); + graph.ID[child] = fnv1a64(bytes); + queue.push(child); + } } - const size = new Float32Array(numNodes); - - for (let i = 0; i < numNodes; i++) { - size[i] = normalize(flamegraph.Samples[i], 0, maxX); + graph.Label[0] = 'root: Represents 100% of CPU time.'; + + for (let i = 1; i < graph.Size; i++) { + const metadata = createStackFrameMetadata({ + FileID: graph.FileID[i], + FrameType: graph.FrameType[i], + ExeFileName: graph.ExeFilename[i], + AddressOrLine: graph.AddressOrLine[i], + FunctionName: graph.FunctionName[i], + FunctionOffset: graph.FunctionOffset[i], + SourceFilename: graph.SourceFilename[i], + SourceLine: graph.SourceLine[i], + }); + graph.Label[i] = getCalleeLabel(metadata); } - return { - label: flamegraph.Label.slice(0, numNodes), - value: Float64Array.from(flamegraph.Samples.slice(0, numNodes)), - color: colors, - position0: position, - position1: position, - size0: size, - size1: size, - } as ColumnarViewModel; + return graph; } diff --git a/x-pack/plugins/profiling/common/hash.test.ts b/x-pack/plugins/profiling/common/hash.test.ts new file mode 100644 index 000000000000..eaec348caa0e --- /dev/null +++ b/x-pack/plugins/profiling/common/hash.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fnv1a64 } from './hash'; + +function toUint8Array(s: string): Uint8Array { + return new TextEncoder().encode(s); +} + +describe('FNV-1a hashing operations', () => { + test('empty', () => { + const input = toUint8Array(''); + const expected = 'cbf29ce484222325'; + + expect(fnv1a64(input)).toEqual(expected); + }); + + test('simple', () => { + const input = toUint8Array('hello world'); + const expected = '779a65e7023cd2e7'; + + expect(fnv1a64(input)).toEqual(expected); + }); + + test('long', () => { + const input = toUint8Array('Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch'); + const expected = '7673401f09f26b0d'; + + expect(fnv1a64(input)).toEqual(expected); + }); + + test('unicode double quotation marks', () => { + const input = toUint8Array('trace:comm = “hello”'); + const expected = '8dada3d28d75245c'; + + expect(fnv1a64(input)).toEqual(expected); + }); + + test('unicode spaces', () => { + const input = toUint8Array('trace:comm\u2000=\u2001"hello"\u3000'); + const expected = '2cdcbb43ff62f74f'; + + expect(fnv1a64(input)).toEqual(expected); + }); +}); diff --git a/x-pack/plugins/profiling/common/hash.ts b/x-pack/plugins/profiling/common/hash.ts new file mode 100644 index 000000000000..3eab4bde871e --- /dev/null +++ b/x-pack/plugins/profiling/common/hash.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// prettier-ignore +const lowerHex = [ + '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', + '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', + '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', + '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', + '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', + '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', + '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', + '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', + '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', + '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', + 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', + 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', + 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', + 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', + 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', + 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff', +]; + +// fnv1a64 computes a 64-bit hash of a byte array using the FNV-1a hash function [1]. +// +// Due to the lack of a native uint64 in JavaScript, we operate on 64-bit values using an array +// of 4 uint16s instead. This method follows Knuth's Algorithm M in section 4.3.1 [2] using a +// modified multiword multiplication implementation described in [3]. The modifications include: +// +// * rewrite default algorithm for the special case m = n = 4 +// * unroll loops +// * simplify expressions +// * create pre-computed lookup table for serialization to hexadecimal +// +// 1. https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +// 2. Knuth, Donald E. The Art of Computer Programming, Volume 2, Third Edition: Seminumerical +// Algorithms. Addison-Wesley, 1998. +// 3. Warren, Henry S. Hacker's Delight. Upper Saddle River, NJ: Addison-Wesley, 2013. + +/* eslint no-bitwise: ["error", { "allow": ["^=", ">>", "&"] }] */ +export function fnv1a64(bytes: Uint8Array): string { + const n = bytes.length; + let [h0, h1, h2, h3] = [0x2325, 0x8422, 0x9ce4, 0xcbf2]; + let [t0, t1, t2, t3] = [0, 0, 0, 0]; + + for (let i = 0; i < n; i++) { + h0 ^= bytes[i]; + + t0 = h0 * 0x01b3; + t1 = h1 * 0x01b3; + t2 = h2 * 0x01b3; + t3 = h3 * 0x01b3; + + t1 += t0 >> 16; + t2 += t1 >> 16; + t2 += h0 * 0x0100; + t3 += h1 * 0x0100; + + h0 = t0 & 0xffff; + h1 = t1 & 0xffff; + h2 = t2 & 0xffff; + h3 = (t3 + (t2 >> 16)) & 0xffff; + } + + return ( + lowerHex[h3 >> 8] + + lowerHex[h3 & 0xff] + + lowerHex[h2 >> 8] + + lowerHex[h2 & 0xff] + + lowerHex[h1 >> 8] + + lowerHex[h1 & 0xff] + + lowerHex[h0 >> 8] + + lowerHex[h0 & 0xff] + ); +} diff --git a/x-pack/plugins/profiling/common/index.ts b/x-pack/plugins/profiling/common/index.ts index 871bc9ee1cd9..01994865abaf 100644 --- a/x-pack/plugins/profiling/common/index.ts +++ b/x-pack/plugins/profiling/common/index.ts @@ -27,7 +27,6 @@ export function getRoutePaths() { TopNThreads: `${BASE_ROUTE_PATH}/topn/threads`, TopNTraces: `${BASE_ROUTE_PATH}/topn/traces`, Flamechart: `${BASE_ROUTE_PATH}/flamechart`, - FrameInformation: `${BASE_ROUTE_PATH}/frame_information`, }; } diff --git a/x-pack/plugins/profiling/common/profiling.ts b/x-pack/plugins/profiling/common/profiling.ts index 6779a1677495..73c0e908ea27 100644 --- a/x-pack/plugins/profiling/common/profiling.ts +++ b/x-pack/plugins/profiling/common/profiling.ts @@ -95,7 +95,7 @@ export interface StackFrameMetadata { // StackTrace.Type FrameType: FrameType; - // StackFrame.LineNumber? + // StackTrace.AddressOrLine AddressOrLine: number; // StackFrame.FunctionName FunctionName: string; @@ -187,7 +187,7 @@ export function getCalleeLabel(metadata: StackFrameMetadata) { if (metadata.FunctionName !== '') { const sourceFilename = metadata.SourceFilename; const sourceURL = sourceFilename ? sourceFilename.split('/').pop() : ''; - return `${getExeFileName(metadata)}: ${getFunctionName(metadata)} in ${sourceURL} #${ + return `${getExeFileName(metadata)}: ${getFunctionName(metadata)} in ${sourceURL}#${ metadata.SourceLine }`; } diff --git a/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx b/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx index 39795474763b..9ab65fee6cf2 100644 --- a/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx +++ b/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx @@ -9,29 +9,30 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, - EuiLoadingSpinner, EuiPanel, EuiText, EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { NOT_AVAILABLE_LABEL } from '../../../common'; -import { AsyncStatus } from '../../hooks/use_async'; import { getImpactRows } from './get_impact_rows'; +import { getInformationRows } from './get_information_rows'; interface Props { frame?: { + fileID: string; + frameType: number; exeFileName: string; + addressOrLine: number; functionName: string; sourceFileName: string; + sourceLine: number; countInclusive: number; countExclusive: number; }; totalSamples: number; totalSeconds: number; onClose: () => void; - status: AsyncStatus; } function KeyValueList({ rows }: { rows: Array<{ label: string; value: React.ReactNode }> }) { @@ -61,11 +62,9 @@ function KeyValueList({ rows }: { rows: Array<{ label: string; value: React.Reac function FlamegraphFrameInformationPanel({ children, onClose, - status, }: { children: React.ReactNode; onClose: () => void; - status: AsyncStatus; }) { return ( @@ -83,11 +82,6 @@ function FlamegraphFrameInformationPanel({ - {status === AsyncStatus.Loading ? ( - - - - ) : undefined} @@ -101,16 +95,10 @@ function FlamegraphFrameInformationPanel({ ); } -export function FlamegraphInformationWindow({ - onClose, - frame, - totalSamples, - totalSeconds, - status, -}: Props) { +export function FlamegraphInformationWindow({ onClose, frame, totalSamples, totalSeconds }: Props) { if (!frame) { return ( - + {i18n.translate('xpack.profiling.flamegraphInformationWindow.selectFrame', { defaultMessage: 'Click on a frame to display more information', @@ -120,7 +108,27 @@ export function FlamegraphInformationWindow({ ); } - const { exeFileName, functionName, sourceFileName, countInclusive, countExclusive } = frame; + const { + fileID, + frameType, + exeFileName, + addressOrLine, + functionName, + sourceFileName, + sourceLine, + countInclusive, + countExclusive, + } = frame; + + const informationRows = getInformationRows({ + fileID, + frameType, + exeFileName, + addressOrLine, + functionName, + sourceFileName, + sourceLine, + }); const impactRows = getImpactRows({ countInclusive, @@ -130,33 +138,10 @@ export function FlamegraphInformationWindow({ }); return ( - + - + diff --git a/x-pack/plugins/profiling/public/components/flame_graphs_view/get_information_rows.ts b/x-pack/plugins/profiling/public/components/flame_graphs_view/get_information_rows.ts new file mode 100644 index 000000000000..d142b3ffd1b5 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/flame_graphs_view/get_information_rows.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { NOT_AVAILABLE_LABEL } from '../../../common'; +import { describeFrameType } from '../../../common/profiling'; + +export function getInformationRows({ + fileID, + frameType, + exeFileName, + addressOrLine, + functionName, + sourceFileName, + sourceLine, +}: { + fileID: string; + frameType: number; + exeFileName: string; + addressOrLine: number; + functionName: string; + sourceFileName: string; + sourceLine: number; +}) { + const executable = fileID === '' && addressOrLine === 0 ? 'root' : exeFileName; + const sourceLineNumber = sourceLine > 0 ? `#${sourceLine}` : ''; + + const informationRows = []; + + if (executable) { + informationRows.push({ + label: i18n.translate('xpack.profiling.flameGraphInformationWindow.executableLabel', { + defaultMessage: 'Executable', + }), + value: executable, + }); + } else { + informationRows.push({ + label: i18n.translate('xpack.profiling.flameGraphInformationWindow.frameTypeLabel', { + defaultMessage: 'Frame type', + }), + value: describeFrameType(frameType), + }); + } + + informationRows.push({ + label: i18n.translate('xpack.profiling.flameGraphInformationWindow.functionLabel', { + defaultMessage: 'Function', + }), + value: functionName || NOT_AVAILABLE_LABEL, + }); + + informationRows.push({ + label: i18n.translate('xpack.profiling.flameGraphInformationWindow.sourceFileLabel', { + defaultMessage: 'Source file', + }), + value: sourceFileName ? `${sourceFileName}${sourceLineNumber}` : NOT_AVAILABLE_LABEL, + }); + + return informationRows; +} diff --git a/x-pack/plugins/profiling/public/components/flame_graphs_view/index.tsx b/x-pack/plugins/profiling/public/components/flame_graphs_view/index.tsx index 6a8802599f6c..dab912902ba4 100644 --- a/x-pack/plugins/profiling/public/components/flame_graphs_view/index.tsx +++ b/x-pack/plugins/profiling/public/components/flame_graphs_view/index.tsx @@ -43,35 +43,40 @@ export function FlameGraphsView({ children }: { children: React.ReactElement }) services: { fetchElasticFlamechart }, } = useProfilingDependencies(); - const state = useTimeRangeAsync(() => { - return Promise.all([ - fetchElasticFlamechart({ - timeFrom: new Date(timeRange.start).getTime() / 1000, - timeTo: new Date(timeRange.end).getTime() / 1000, - kuery, - }), - comparisonTimeRange.start && comparisonTimeRange.end - ? fetchElasticFlamechart({ - timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000, - timeTo: new Date(comparisonTimeRange.end).getTime() / 1000, - kuery: comparisonKuery, - }) - : Promise.resolve(undefined), - ]).then(([primaryFlamegraph, comparisonFlamegraph]) => { - return { - primaryFlamegraph, - comparisonFlamegraph, - }; - }); - }, [ - timeRange.start, - timeRange.end, - kuery, - comparisonTimeRange.start, - comparisonTimeRange.end, - comparisonKuery, - fetchElasticFlamechart, - ]); + const state = useTimeRangeAsync( + ({ http }) => { + return Promise.all([ + fetchElasticFlamechart({ + http, + timeFrom: new Date(timeRange.start).getTime() / 1000, + timeTo: new Date(timeRange.end).getTime() / 1000, + kuery, + }), + comparisonTimeRange.start && comparisonTimeRange.end + ? fetchElasticFlamechart({ + http, + timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000, + timeTo: new Date(comparisonTimeRange.end).getTime() / 1000, + kuery: comparisonKuery, + }) + : Promise.resolve(undefined), + ]).then(([primaryFlamegraph, comparisonFlamegraph]) => { + return { + primaryFlamegraph, + comparisonFlamegraph, + }; + }); + }, + [ + timeRange.start, + timeRange.end, + kuery, + comparisonTimeRange.start, + comparisonTimeRange.end, + comparisonKuery, + fetchElasticFlamechart, + ] + ); const { data } = state; @@ -173,7 +178,6 @@ export function FlameGraphsView({ children }: { children: React.ReactElement }) = ({ id, - height, comparisonMode, primaryFlamegraph, comparisonFlamegraph, }) => { const theme = useEuiTheme(); - const { - services: { fetchFrameInformation }, - } = useProfilingDependencies(); - const columnarData = useMemo(() => { return getFlamegraphModel({ primaryFlamegraph, @@ -193,38 +185,17 @@ export const FlameGraph: React.FC = ({ const [highlightedVmIndex, setHighlightedVmIndex] = useState(undefined); - const highlightedFrameQueryParams = useMemo(() => { - if (!primaryFlamegraph || highlightedVmIndex === undefined || highlightedVmIndex === 0) { - return undefined; - } - - const frameID = primaryFlamegraph.FrameID[highlightedVmIndex]; - const executableID = primaryFlamegraph.ExecutableID[highlightedVmIndex]; - - return { - frameID, - executableID, - }; - }, [primaryFlamegraph, highlightedVmIndex]); - - const { data: highlightedFrame, status: highlightedFrameStatus } = useAsync(() => { - if (!highlightedFrameQueryParams) { - return Promise.resolve(undefined); - } - - return fetchFrameInformation({ - frameID: highlightedFrameQueryParams.frameID, - executableID: highlightedFrameQueryParams.executableID, - }); - }, [highlightedFrameQueryParams, fetchFrameInformation]); - const selected: undefined | React.ComponentProps['frame'] = - primaryFlamegraph && highlightedFrame && highlightedVmIndex !== undefined + primaryFlamegraph && highlightedVmIndex !== undefined ? { - exeFileName: highlightedFrame.ExeFileName, - sourceFileName: highlightedFrame.SourceFilename, - functionName: highlightedFrame.FunctionName, - countInclusive: primaryFlamegraph.Samples[highlightedVmIndex], + fileID: primaryFlamegraph.FileID[highlightedVmIndex], + frameType: primaryFlamegraph.FrameType[highlightedVmIndex], + exeFileName: primaryFlamegraph.ExeFilename[highlightedVmIndex], + addressOrLine: primaryFlamegraph.AddressOrLine[highlightedVmIndex], + functionName: primaryFlamegraph.FunctionName[highlightedVmIndex], + sourceFileName: primaryFlamegraph.SourceFilename[highlightedVmIndex], + sourceLine: primaryFlamegraph.SourceLine[highlightedVmIndex], + countInclusive: primaryFlamegraph.CountInclusive[highlightedVmIndex], countExclusive: primaryFlamegraph.CountExclusive[highlightedVmIndex], } : undefined; @@ -271,7 +242,7 @@ export const FlameGraph: React.FC = ({ const valueIndex = props.values[0].valueAccessor as number; const label = primaryFlamegraph.Label[valueIndex]; - const samples = primaryFlamegraph.Samples[valueIndex]; + const samples = primaryFlamegraph.CountInclusive[valueIndex]; const countInclusive = primaryFlamegraph.CountInclusive[valueIndex]; const countExclusive = primaryFlamegraph.CountExclusive[valueIndex]; const nodeID = primaryFlamegraph.ID[valueIndex]; @@ -287,8 +258,8 @@ export const FlameGraph: React.FC = ({ comparisonCountInclusive={comparisonNode?.CountInclusive} comparisonCountExclusive={comparisonNode?.CountExclusive} totalSamples={totalSamples} - comparisonTotalSamples={comparisonFlamegraph?.Samples[0]} - comparisonSamples={comparisonNode?.Samples} + comparisonTotalSamples={comparisonFlamegraph?.CountInclusive[0]} + comparisonSamples={comparisonNode?.CountInclusive} /> ); }, @@ -309,7 +280,6 @@ export const FlameGraph: React.FC = ({ { diff --git a/x-pack/plugins/profiling/public/components/functions_view/index.tsx b/x-pack/plugins/profiling/public/components/functions_view/index.tsx index 94410f961c46..2c7b25754f3d 100644 --- a/x-pack/plugins/profiling/public/components/functions_view/index.tsx +++ b/x-pack/plugins/profiling/public/components/functions_view/index.tsx @@ -42,28 +42,36 @@ export function FunctionsView({ children }: { children: React.ReactElement }) { services: { fetchTopNFunctions }, } = useProfilingDependencies(); - const state = useTimeRangeAsync(() => { - return fetchTopNFunctions({ - timeFrom: new Date(timeRange.start).getTime() / 1000, - timeTo: new Date(timeRange.end).getTime() / 1000, - startIndex: 0, - endIndex: 1000, - kuery, - }); - }, [timeRange.start, timeRange.end, kuery, fetchTopNFunctions]); + const state = useTimeRangeAsync( + ({ http }) => { + return fetchTopNFunctions({ + http, + timeFrom: new Date(timeRange.start).getTime() / 1000, + timeTo: new Date(timeRange.end).getTime() / 1000, + startIndex: 0, + endIndex: 1000, + kuery, + }); + }, + [timeRange.start, timeRange.end, kuery, fetchTopNFunctions] + ); - const comparisonState = useTimeRangeAsync(() => { - if (!comparisonTimeRange.start || !comparisonTimeRange.end) { - return undefined; - } - return fetchTopNFunctions({ - timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000, - timeTo: new Date(comparisonTimeRange.end).getTime() / 1000, - startIndex: 0, - endIndex: 1000, - kuery: comparisonKuery, - }); - }, [comparisonTimeRange.start, comparisonTimeRange.end, comparisonKuery, fetchTopNFunctions]); + const comparisonState = useTimeRangeAsync( + ({ http }) => { + if (!comparisonTimeRange.start || !comparisonTimeRange.end) { + return undefined; + } + return fetchTopNFunctions({ + http, + timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000, + timeTo: new Date(comparisonTimeRange.end).getTime() / 1000, + startIndex: 0, + endIndex: 1000, + kuery: comparisonKuery, + }); + }, + [comparisonTimeRange.start, comparisonTimeRange.end, comparisonKuery, fetchTopNFunctions] + ); const routePath = useProfilingRoutePath() as | '/functions' diff --git a/x-pack/plugins/profiling/public/components/stack_traces_view/index.tsx b/x-pack/plugins/profiling/public/components/stack_traces_view/index.tsx index 23bb4a7bd4b6..19834410c6b7 100644 --- a/x-pack/plugins/profiling/public/components/stack_traces_view/index.tsx +++ b/x-pack/plugins/profiling/public/components/stack_traces_view/index.tsx @@ -50,29 +50,33 @@ export function StackTracesView() { rangeTo, }); - const state = useTimeRangeAsync(() => { - if (!topNType) { - return Promise.resolve({ charts: [], metadata: {} }); - } - return fetchTopN({ - type: topNType, - timeFrom: new Date(timeRange.start).getTime() / 1000, - timeTo: new Date(timeRange.end).getTime() / 1000, - kuery, - }).then((response: TopNResponse) => { - const totalCount = response.TotalCount; - const samples = response.TopN; - const charts = groupSamplesByCategory({ - samples, - totalCount, - metadata: response.Metadata, - labels: response.Labels, + const state = useTimeRangeAsync( + ({ http }) => { + if (!topNType) { + return Promise.resolve({ charts: [], metadata: {} }); + } + return fetchTopN({ + http, + type: topNType, + timeFrom: new Date(timeRange.start).getTime() / 1000, + timeTo: new Date(timeRange.end).getTime() / 1000, + kuery, + }).then((response: TopNResponse) => { + const totalCount = response.TotalCount; + const samples = response.TopN; + const charts = groupSamplesByCategory({ + samples, + totalCount, + metadata: response.Metadata, + labels: response.Labels, + }); + return { + charts, + }; }); - return { - charts, - }; - }); - }, [topNType, timeRange.start, timeRange.end, fetchTopN, kuery]); + }, + [topNType, timeRange.start, timeRange.end, fetchTopN, kuery] + ); const [highlightedSubchart, setHighlightedSubchart] = useState( undefined diff --git a/x-pack/plugins/profiling/public/components/topn_functions.tsx b/x-pack/plugins/profiling/public/components/topn_functions.tsx index 4d8522913a24..8421b99b5dc7 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions.tsx +++ b/x-pack/plugins/profiling/public/components/topn_functions.tsx @@ -144,6 +144,9 @@ export const TopNFunctionsTable = ({ defaultMessage: 'Samples', }), align: 'right', + render: (_, { samples }) => { + return {samples}; + }, }, { field: TopNFunctionSortField.ExclusiveCPU, diff --git a/x-pack/plugins/profiling/public/hooks/use_async.ts b/x-pack/plugins/profiling/public/hooks/use_async.ts index ea6da578c387..7156b4e8bfff 100644 --- a/x-pack/plugins/profiling/public/hooks/use_async.ts +++ b/x-pack/plugins/profiling/public/hooks/use_async.ts @@ -4,8 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { HttpStart } from '@kbn/core-http-browser'; -import { useEffect, useState } from 'react'; +import { HttpFetchOptions, HttpHandler, HttpStart } from '@kbn/core-http-browser'; +import { AbortError } from '@kbn/kibana-utils-plugin/common'; +import { useEffect, useRef, useState } from 'react'; +import { Overwrite, ValuesType } from 'utility-types'; import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies'; export enum AsyncStatus { @@ -20,8 +22,22 @@ export interface AsyncState { status: AsyncStatus; } +const HTTP_METHODS = ['fetch', 'get', 'post', 'put', 'delete', 'patch'] as const; + +type HttpMethod = ValuesType; + +type AutoAbortedHttpMethod = ( + path: string, + options: Omit +) => ReturnType; + +export type AutoAbortedHttpService = Overwrite< + HttpStart, + Record +>; + export type UseAsync = ( - fn: ({ http }: { http: HttpStart }) => Promise | undefined, + fn: ({ http }: { http: AutoAbortedHttpService }) => Promise | undefined, dependencies: any[] ) => AsyncState; @@ -37,8 +53,30 @@ export const useAsync: UseAsync = (fn, dependencies) => { const { data, error } = asyncState; + const controllerRef = useRef(new AbortController()); + useEffect(() => { - const returnValue = fn({ http }); + controllerRef.current.abort(); + + controllerRef.current = new AbortController(); + + const autoAbortedMethods = {} as Record; + + for (const key of HTTP_METHODS) { + autoAbortedMethods[key] = (path, options) => { + return http[key](path, { ...options, signal: controllerRef.current.signal }).catch( + (err) => { + if (err.name === 'AbortError') { + // return never-resolving promise + return new Promise(() => {}); + } + throw err; + } + ); + }; + } + + const returnValue = fn({ http: { ...http, ...autoAbortedMethods } }); if (returnValue === undefined) { setAsyncState({ @@ -63,13 +101,23 @@ export const useAsync: UseAsync = (fn, dependencies) => { }); returnValue.catch((nextError) => { + if (nextError instanceof AbortError) { + return; + } setAsyncState({ status: AsyncStatus.Settled, error: nextError, }); + throw nextError; }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [http, ...dependencies]); + useEffect(() => { + return () => { + controllerRef.current.abort(); + }; + }, []); + return asyncState; }; diff --git a/x-pack/plugins/profiling/public/plugin.tsx b/x-pack/plugins/profiling/public/plugin.tsx index 080941729b01..ead023a079e2 100644 --- a/x-pack/plugins/profiling/public/plugin.tsx +++ b/x-pack/plugins/profiling/public/plugin.tsx @@ -90,7 +90,7 @@ export class ProfilingPlugin implements Plugin { unknown ]; - const profilingFetchServices = getServices(coreStart); + const profilingFetchServices = getServices(); const { renderApp } = await import('./app'); function pushKueryToSubject(location: Location) { diff --git a/x-pack/plugins/profiling/public/services.ts b/x-pack/plugins/profiling/public/services.ts index 07234ca124b3..4dcad550d0bb 100644 --- a/x-pack/plugins/profiling/public/services.ts +++ b/x-pack/plugins/profiling/public/services.ts @@ -4,22 +4,23 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { CoreStart, HttpFetchQuery } from '@kbn/core/public'; +import { HttpFetchQuery } from '@kbn/core/public'; import { getRoutePaths } from '../common'; -import { ElasticFlameGraph } from '../common/flamegraph'; +import { BaseFlameGraph, createFlameGraph, ElasticFlameGraph } from '../common/flamegraph'; import { TopNFunctions } from '../common/functions'; -import { StackFrameMetadata } from '../common/profiling'; import { TopNResponse } from '../common/topn'; +import { AutoAbortedHttpService } from './hooks/use_async'; export interface Services { fetchTopN: (params: { + http: AutoAbortedHttpService; type: string; timeFrom: number; timeTo: number; kuery: string; }) => Promise; fetchTopNFunctions: (params: { + http: AutoAbortedHttpService; timeFrom: number; timeTo: number; startIndex: number; @@ -27,46 +28,31 @@ export interface Services { kuery: string; }) => Promise; fetchElasticFlamechart: (params: { + http: AutoAbortedHttpService; timeFrom: number; timeTo: number; kuery: string; }) => Promise; - fetchFrameInformation: (params: { - frameID: string; - executableID: string; - }) => Promise; } -export function getServices(core: CoreStart): Services { +export function getServices(): Services { const paths = getRoutePaths(); return { - fetchTopN: async ({ type, timeFrom, timeTo, kuery }) => { + fetchTopN: async ({ http, type, timeFrom, timeTo, kuery }) => { try { const query: HttpFetchQuery = { timeFrom, timeTo, kuery, }; - return await core.http.get(`${paths.TopN}/${type}`, { query }); + return await http.get(`${paths.TopN}/${type}`, { query }); } catch (e) { return e; } }, - fetchTopNFunctions: async ({ - timeFrom, - timeTo, - startIndex, - endIndex, - kuery, - }: { - timeFrom: number; - timeTo: number; - startIndex: number; - endIndex: number; - kuery: string; - }) => { + fetchTopNFunctions: async ({ http, timeFrom, timeTo, startIndex, endIndex, kuery }) => { try { const query: HttpFetchQuery = { timeFrom, @@ -75,45 +61,21 @@ export function getServices(core: CoreStart): Services { endIndex, kuery, }; - return await core.http.get(paths.TopNFunctions, { query }); + return await http.get(paths.TopNFunctions, { query }); } catch (e) { return e; } }, - fetchElasticFlamechart: async ({ - timeFrom, - timeTo, - kuery, - }: { - timeFrom: number; - timeTo: number; - kuery: string; - }) => { + fetchElasticFlamechart: async ({ http, timeFrom, timeTo, kuery }) => { try { const query: HttpFetchQuery = { timeFrom, timeTo, kuery, }; - return await core.http.get(paths.Flamechart, { query }); - } catch (e) { - return e; - } - }, - fetchFrameInformation: async ({ - frameID, - executableID, - }: { - frameID: string; - executableID: string; - }) => { - try { - const query: HttpFetchQuery = { - frameID, - executableID, - }; - return await core.http.get(paths.FrameInformation, { query }); + const baseFlamegraph = (await http.get(paths.Flamechart, { query })) as BaseFlameGraph; + return createFlameGraph(baseFlamegraph); } catch (e) { return e; } diff --git a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts index c63a73185d26..4cda7befe44c 100644 --- a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts +++ b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts @@ -6,12 +6,8 @@ */ import d3 from 'd3'; import { sum, uniqueId } from 'lodash'; -import { - createColumnarViewModel, - ElasticFlameGraph, - FlameGraphComparisonMode, - rgbToRGBA, -} from '../../../common/flamegraph'; +import { createColumnarViewModel, rgbToRGBA } from '../../../common/columnar_view_model'; +import { ElasticFlameGraph, FlameGraphComparisonMode } from '../../../common/flamegraph'; import { getInterpolationValue } from './get_interpolation_value'; const nullColumnarViewModel = { @@ -39,10 +35,8 @@ export function getFlamegraphModel({ colorNeutral: string; comparisonMode: FlameGraphComparisonMode; }) { - const comparisonNodesById: Record< - string, - { Samples: number; CountInclusive: number; CountExclusive: number } - > = {}; + const comparisonNodesById: Record = + {}; if (!primaryFlamegraph || !primaryFlamegraph.Label || primaryFlamegraph.Label.length === 0) { return { key: uniqueId(), viewModel: nullColumnarViewModel, comparisonNodesById }; @@ -53,7 +47,6 @@ export function getFlamegraphModel({ if (comparisonFlamegraph) { comparisonFlamegraph.ID.forEach((nodeID, index) => { comparisonNodesById[nodeID] = { - Samples: comparisonFlamegraph.Samples[index], CountInclusive: comparisonFlamegraph.CountInclusive[index], CountExclusive: comparisonFlamegraph.CountExclusive[index], }; @@ -88,8 +81,8 @@ export function getFlamegraphModel({ : primaryFlamegraph.TotalSeconds / comparisonFlamegraph.TotalSeconds; primaryFlamegraph.ID.forEach((nodeID, index) => { - const samples = primaryFlamegraph.Samples[index]; - const comparisonSamples = comparisonNodesById[nodeID]?.Samples as number | undefined; + const samples = primaryFlamegraph.CountInclusive[index]; + const comparisonSamples = comparisonNodesById[nodeID]?.CountInclusive as number | undefined; const foreground = comparisonMode === FlameGraphComparisonMode.Absolute ? samples : samples / totalSamples; diff --git a/x-pack/plugins/profiling/server/routes/flamechart.ts b/x-pack/plugins/profiling/server/routes/flamechart.ts index 6d27305a82c6..49f0415049ad 100644 --- a/x-pack/plugins/profiling/server/routes/flamechart.ts +++ b/x-pack/plugins/profiling/server/routes/flamechart.ts @@ -9,7 +9,8 @@ import { schema } from '@kbn/config-schema'; import { RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; import { createCalleeTree } from '../../common/callee'; -import { createFlameGraph } from '../../common/flamegraph'; +import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; +import { createBaseFlameGraph } from '../../common/flamegraph'; import { createProfilingEsClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; @@ -42,20 +43,13 @@ export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterP }); const totalSeconds = timeTo - timeFrom; - const { - stackTraces, - executables, - stackFrames, - eventsIndex, - totalCount, - totalFrames, - stackTraceEvents, - } = await getExecutablesAndStackTraces({ - logger, - client: createProfilingEsClient({ request, esClient }), - filter, - sampleSize: targetSampleSize, - }); + const { stackTraceEvents, stackTraces, executables, stackFrames, totalFrames } = + await getExecutablesAndStackTraces({ + logger, + client: createProfilingEsClient({ request, esClient }), + filter, + sampleSize: targetSampleSize, + }); const flamegraph = await withProfilingSpan('create_flamegraph', async () => { const t0 = Date.now(); @@ -68,23 +62,8 @@ export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterP ); logger.info(`creating callee tree took ${Date.now() - t0} ms`); - // sampleRate is 1/5^N, with N being the downsampled index the events were fetched from. - // N=0: full events table (sampleRate is 1) - // N=1: downsampled by 5 (sampleRate is 0.2) - // ... - - // totalCount is the sum(Count) of all events in the filter range in the - // downsampled index we were looking at. - // To estimate how many events we have in the full events index: totalCount / sampleRate. - // Do the same for single entries in the events array. - const t1 = Date.now(); - const fg = createFlameGraph( - tree, - totalSeconds, - Math.floor(totalCount / eventsIndex.sampleRate), - totalCount - ); + const fg = createBaseFlameGraph(tree, totalSeconds); logger.info(`creating flamegraph took ${Date.now() - t1} ms`); return fg; @@ -93,14 +72,8 @@ export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterP logger.info('returning payload response to client'); return response.ok({ body: flamegraph }); - } catch (e) { - logger.error(e); - return response.customError({ - statusCode: e.statusCode ?? 500, - body: { - message: e.message, - }, - }); + } catch (error) { + return handleRouteHandlerError({ error, logger, response }); } } ); diff --git a/x-pack/plugins/profiling/server/routes/frames.ts b/x-pack/plugins/profiling/server/routes/frames.ts deleted file mode 100644 index 4a0ce745c724..000000000000 --- a/x-pack/plugins/profiling/server/routes/frames.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; -import { Logger } from '@kbn/logging'; -import { RouteRegisterParameters } from '.'; -import { getRoutePaths } from '../../common'; -import { - createStackFrameMetadata, - Executable, - StackFrame, - StackFrameMetadata, -} from '../../common/profiling'; -import { createProfilingEsClient, ProfilingESClient } from '../utils/create_profiling_es_client'; -import { mgetStackFrames, mgetExecutables } from './stacktrace'; - -async function getFrameInformation({ - frameID, - executableID, - logger, - client, -}: { - frameID: string; - executableID: string; - logger: Logger; - client: ProfilingESClient; -}): Promise { - const [stackFrames, executables] = await Promise.all([ - mgetStackFrames({ - logger, - client, - stackFrameIDs: new Set([frameID]), - }), - mgetExecutables({ - logger, - client, - executableIDs: new Set([executableID]), - }), - ]); - - const frame = Array.from(stackFrames.values())[0] as StackFrame | undefined; - const executable = Array.from(executables.values())[0] as Executable | undefined; - - if (frame) { - return createStackFrameMetadata({ - FrameID: frameID, - FileID: executableID, - SourceFilename: frame.FileName, - FunctionName: frame.FunctionName, - ExeFileName: executable?.FileName, - }); - } -} - -export function registerFrameInformationRoute(params: RouteRegisterParameters) { - const { logger, router } = params; - - const routePaths = getRoutePaths(); - - router.get( - { - path: routePaths.FrameInformation, - validate: { - query: schema.object({ - frameID: schema.string(), - executableID: schema.string(), - }), - }, - }, - async (context, request, response) => { - const { frameID, executableID } = request.query; - - const client = createProfilingEsClient({ - request, - esClient: (await context.core).elasticsearch.client.asCurrentUser, - }); - - try { - const frame = await getFrameInformation({ - frameID, - executableID, - logger, - client, - }); - - return response.ok({ body: frame }); - } catch (error: any) { - logger.error(error); - return response.custom({ - statusCode: error.statusCode ?? 500, - body: { - message: error.message ?? 'An internal server error occured', - }, - }); - } - } - ); -} diff --git a/x-pack/plugins/profiling/server/routes/functions.ts b/x-pack/plugins/profiling/server/routes/functions.ts index adbbc5900337..9d3eed222b90 100644 --- a/x-pack/plugins/profiling/server/routes/functions.ts +++ b/x-pack/plugins/profiling/server/routes/functions.ts @@ -9,6 +9,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; import { createTopNFunctions } from '../../common/functions'; +import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; import { createProfilingEsClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; @@ -72,14 +73,8 @@ export function registerTopNFunctionsSearchRoute({ router, logger }: RouteRegist return response.ok({ body: topNFunctions, }); - } catch (e) { - logger.error(e); - return response.customError({ - statusCode: e.statusCode ?? 500, - body: { - message: e.message, - }, - }); + } catch (error) { + return handleRouteHandlerError({ error, logger, response }); } } ); diff --git a/x-pack/plugins/profiling/server/routes/index.ts b/x-pack/plugins/profiling/server/routes/index.ts index 6e44bf690958..b6bd705ba0e0 100644 --- a/x-pack/plugins/profiling/server/routes/index.ts +++ b/x-pack/plugins/profiling/server/routes/index.ts @@ -13,7 +13,6 @@ import { } from '../types'; import { registerFlameChartSearchRoute } from './flamechart'; -import { registerFrameInformationRoute } from './frames'; import { registerTopNFunctionsSearchRoute } from './functions'; import { @@ -41,5 +40,4 @@ export function registerRoutes(params: RouteRegisterParameters) { registerTraceEventsTopNHostsSearchRoute(params); registerTraceEventsTopNStackTracesSearchRoute(params); registerTraceEventsTopNThreadsSearchRoute(params); - registerFrameInformationRoute(params); } diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts index a40f72d25f2d..5dd3f1985a35 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts @@ -118,6 +118,14 @@ describe('Stack trace operations', () => { } }); + test('runLengthDecode with larger output than available input', () => { + const bytes = Buffer.from([0x5, 0x0, 0x2, 0x2]); + const decoded = [0, 0, 0, 0, 0, 2, 2]; + const expected = decoded.concat(Array(decoded.length).fill(0)); + + expect(runLengthDecode(bytes, expected.length)).toEqual(expected); + }); + test('runLengthDecode without optional parameter', () => { const tests: Array<{ bytes: Buffer; diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts index 4ae7d91596f1..6dbe063e2c4f 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.ts @@ -122,6 +122,17 @@ export function runLengthDecode(input: Buffer, outputSize?: number): number[] { } } + // Due to truncation of the frame types for stacktraces longer than 255, + // the expected output size and the actual decoded size can be different. + // Ordinarily, these two values should be the same. + // + // We have decided to fill in the remainder of the output array with zeroes + // as a reasonable default. Without this step, the output array would have + // undefined values. + for (let i = idx; i < size; i++) { + output[i] = 0; + } + return output; } diff --git a/x-pack/plugins/profiling/server/routes/topn.ts b/x-pack/plugins/profiling/server/routes/topn.ts index 790f40049e16..a8a7efc01bb5 100644 --- a/x-pack/plugins/profiling/server/routes/topn.ts +++ b/x-pack/plugins/profiling/server/routes/topn.ts @@ -14,6 +14,7 @@ import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../common/hist import { groupStackFrameMetadataByStackTrace, StackTraceID } from '../../common/profiling'; import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces'; import { createTopNSamples, getTopNAggregationRequest, TopNResponse } from '../../common/topn'; +import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; import { ProfilingRequestHandlerContext } from '../types'; import { createProfilingEsClient, ProfilingESClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; @@ -189,15 +190,8 @@ export function queryTopNCommon( kuery, }), }); - } catch (e) { - logger.error(e); - - return response.customError({ - statusCode: e.statusCode ?? 500, - body: { - message: 'Profiling TopN request failed: ' + e.message + '; full error ' + e.toString(), - }, - }); + } catch (error) { + return handleRouteHandlerError({ error, logger, response }); } } ); diff --git a/x-pack/plugins/profiling/server/utils/handle_route_error_handler.ts b/x-pack/plugins/profiling/server/utils/handle_route_error_handler.ts new file mode 100644 index 000000000000..782beeeae15c --- /dev/null +++ b/x-pack/plugins/profiling/server/utils/handle_route_error_handler.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KibanaResponseFactory } from '@kbn/core-http-server'; +import { Logger } from '@kbn/logging'; +import { WrappedElasticsearchClientError } from '@kbn/observability-plugin/server'; +import { errors } from '@elastic/elasticsearch'; + +export function handleRouteHandlerError({ + error, + logger, + response, +}: { + error: any; + response: KibanaResponseFactory; + logger: Logger; +}) { + if ( + error instanceof WrappedElasticsearchClientError && + error.originalError instanceof errors.RequestAbortedError + ) { + return response.custom({ + statusCode: 499, + body: { + message: 'Client closed request', + }, + }); + } + logger.error(error); + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message, + }, + }); +} diff --git a/x-pack/plugins/reporting/public/management/components/report_diagnostic.tsx b/x-pack/plugins/reporting/public/management/components/report_diagnostic.tsx index 22236f77ffda..b535a8a94a3a 100644 --- a/x-pack/plugins/reporting/public/management/components/report_diagnostic.tsx +++ b/x-pack/plugins/reporting/public/management/components/report_diagnostic.tsx @@ -16,6 +16,7 @@ import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, + EuiMarkdownFormat, EuiSpacer, EuiSteps, EuiText, @@ -178,7 +179,9 @@ export const ReportDiagnostic = ({ apiClient }: Props) => { {help.length ? ( -

    {help.join('\n')}

    +

    + {help.join('\n')} +

    ) : null} diff --git a/x-pack/plugins/rule_registry/README.md b/x-pack/plugins/rule_registry/README.md index 6ca34fc9ece1..e0d79482e29f 100644 --- a/x-pack/plugins/rule_registry/README.md +++ b/x-pack/plugins/rule_registry/README.md @@ -143,6 +143,7 @@ The following fields are defined in the technical field component template and s - `kibana.alert.ancestors`: the array of ancestors (if any) for the alert. - `kibana.alert.depth`: the depth of the alert in the ancestral tree (default 0). - `kibana.alert.building_block_type`: the building block type of the alert (default undefined). +- `kibana.alert.time_range`: the time range of an alert. (default undefined). # Alerts as data diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index 06f00d9b6e6f..32406f7a87fc 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -198,6 +198,10 @@ it('matches snapshot', () => { "required": false, "type": "keyword", }, + "kibana.alert.time_range": Object { + "format": "epoch_millis||strict_date_optional_time", + "type": "date_range", + }, "kibana.alert.uuid": Object { "required": true, "type": "keyword", diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index ba1703b8be5d..2233f2d97701 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -25,6 +25,10 @@ export const technicalRuleFieldMap = { [Fields.ALERT_UUID]: { type: 'keyword', required: true }, [Fields.ALERT_INSTANCE_ID]: { type: 'keyword', required: true }, [Fields.ALERT_START]: { type: 'date' }, + [Fields.ALERT_TIME_RANGE]: { + type: 'date_range', + format: 'epoch_millis||strict_date_optional_time', + }, [Fields.ALERT_END]: { type: 'date' }, [Fields.ALERT_DURATION]: { type: 'long' }, [Fields.ALERT_SEVERITY]: { type: 'keyword' }, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index da68ef3c4c7b..160e06d03e92 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -22,6 +22,7 @@ import { import { ParsedExperimentalFields } from '../../common/parse_experimental_fields'; import { ParsedTechnicalFields } from '../../common/parse_technical_fields'; import { + ALERT_TIME_RANGE, ALERT_DURATION, ALERT_END, ALERT_INSTANCE_ID, @@ -235,7 +236,12 @@ export const createLifecycleExecutor = ...commonRuleFields, ...currentAlertData, [ALERT_DURATION]: (options.startedAt.getTime() - new Date(started).getTime()) * 1000, - + [ALERT_TIME_RANGE]: isRecovered + ? { + gte: started, + lte: commonRuleFields[TIMESTAMP], + } + : { gte: started }, [ALERT_INSTANCE_ID]: alertId, [ALERT_START]: started, [ALERT_UUID]: alertUuid, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index 6a3660494d18..acb12645cbae 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -12,6 +12,7 @@ import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, ALERT_UUID, + ALERT_TIME_RANGE, } from '@kbn/rule-data-utils'; import { loggerMock } from '@kbn/logging-mocks'; import { castArray, omit } from 'lodash'; @@ -245,6 +246,9 @@ describe('createLifecycleRuleTypeFactory', () => { "kibana.alert.rule.uuid": "alertId", "kibana.alert.start": "2021-06-16T09:01:00.000Z", "kibana.alert.status": "active", + "kibana.alert.time_range": Object { + "gte": "2021-06-16T09:01:00.000Z", + }, "kibana.alert.workflow_status": "open", "kibana.space_ids": Array [ "spaceId", @@ -273,6 +277,9 @@ describe('createLifecycleRuleTypeFactory', () => { "kibana.alert.rule.uuid": "alertId", "kibana.alert.start": "2021-06-16T09:01:00.000Z", "kibana.alert.status": "active", + "kibana.alert.time_range": Object { + "gte": "2021-06-16T09:01:00.000Z", + }, "kibana.alert.workflow_status": "open", "kibana.space_ids": Array [ "spaceId", @@ -443,6 +450,10 @@ describe('createLifecycleRuleTypeFactory', () => { expect(opbeansNodeAlertDoc['event.action']).toBe('close'); expect(opbeansNodeAlertDoc[ALERT_STATUS]).toBe(ALERT_STATUS_RECOVERED); + expect(opbeansNodeAlertDoc[ALERT_TIME_RANGE]).toEqual({ + gte: '2021-06-16T09:01:00.000Z', + lte: '2021-06-16T09:02:00.000Z', + }); }); }); }); diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap index 97197720ce59..da3adab8b5f0 100644 --- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

    Some Title

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

    Some Title

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

    Some Title

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

    Some Title

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

    We couldn't log you in

    We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.

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

    We couldn't log you in

    We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.

    "`; diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index 3b553c7131df..f987be427a5c 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

    You do not have permission to access the requested page

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

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

    You do not have permission to access the requested page

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

    "`; diff --git a/x-pack/plugins/security/server/prompt_page.tsx b/x-pack/plugins/security/server/prompt_page.tsx index 14f59df15db3..38bd77b444e8 100644 --- a/x-pack/plugins/security/server/prompt_page.tsx +++ b/x-pack/plugins/security/server/prompt_page.tsx @@ -17,8 +17,10 @@ import { icon as EuiIconAlert } from '@elastic/eui/lib/components/icon/assets/al // @ts-expect-error no definitions in component folder import { appendIconComponentCache } from '@elastic/eui/lib/components/icon/icon'; import createCache from '@emotion/cache'; +import createEmotionServer from '@emotion/server/create-instance'; import type { ReactNode } from 'react'; import React from 'react'; +import { renderToString } from 'react-dom/server'; import { Fonts } from '@kbn/core-rendering-server-internal'; import type { IBasePath } from '@kbn/core/server'; @@ -34,6 +36,8 @@ appendIconComponentCache({ alert: EuiIconAlert, }); +const emotionCache = createCache({ key: 'eui' }); + interface Props { buildNumber: number; basePath: IBasePath; @@ -51,6 +55,31 @@ export function PromptPage({ body, actions, }: Props) { + const content = ( + + + + + + {title}} + body={body} + actions={actions} + /> + + + + + + ); + + const { extractCriticalToChunks, constructStyleTagsFromChunks } = + createEmotionServer(emotionCache); + const chunks = extractCriticalToChunks(renderToString(content)); + const emotionStyles = constructStyleTagsFromChunks(chunks); + const uiPublicURL = `${basePath.serverBasePath}/ui`; const regularBundlePath = `${basePath.serverBasePath}/${buildNumber}/bundles`; const styleSheetPaths = [ @@ -60,16 +89,12 @@ export function PromptPage({ `${basePath.serverBasePath}/ui/legacy_light_theme.css`, ]; - // Emotion SSR styles will be prepended to the and emit a console log warning about :first-child selectors - const emotionCache = createCache({ - key: 'css', - prepend: true, - }); - return ( Elastic + {/* eslint-disable-next-line react/no-danger */} +