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/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/.github/CODEOWNERS b/.github/CODEOWNERS index b7d588bc8926..ca8e8c07fec6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -504,6 +504,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 @@ -745,6 +750,9 @@ packages/core/http/core-http-context-server-internal @elastic/kibana-core packages/core/http/core-http-context-server-mocks @elastic/kibana-core packages/core/http/core-http-request-handler-context-server @elastic/kibana-core packages/core/http/core-http-request-handler-context-server-internal @elastic/kibana-core +packages/core/http/core-http-resources-server @elastic/kibana-core +packages/core/http/core-http-resources-server-internal @elastic/kibana-core +packages/core/http/core-http-resources-server-mocks @elastic/kibana-core packages/core/http/core-http-router-server-internal @elastic/kibana-core packages/core/http/core-http-router-server-mocks @elastic/kibana-core packages/core/http/core-http-server @elastic/kibana-core diff --git a/api_docs/actions.devdocs.json b/api_docs/actions.devdocs.json index cefd0ffd4d6b..86bf3911c319 100644 --- a/api_docs/actions.devdocs.json +++ b/api_docs/actions.devdocs.json @@ -858,6 +858,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 +1226,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, diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index f845dffebd44..311bee3f6ba9 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-05 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 | +| 216 | 0 | 211 | 21 | ## Client diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index c624a6191fca..58312f0eaa62 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-05 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..0b0c41380b9d 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-05 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..6757b8a06f9a 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -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..558413407b38 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index c5590b95953e..bee8505fd83c 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-05 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..00b9a4694465 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-05 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..8a65db4587c7 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-05 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..661b740bfeae 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-05 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..d79be9464f7f 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-05 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..7057a6424a74 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-05 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..e0ed849f6849 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-05 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..0c6772023c28 --- /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-05 +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..6d30ae17a1e7 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-05 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..ed1aefa51347 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-05 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..ddb1f141f152 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-05 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..3e93cc800acc 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-05 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..10cb0b13616f 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", @@ -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", @@ -44308,6 +44295,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 +51910,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 +51986,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 +53707,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, diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 48204394f0a8..d0e7e1126e5f 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-05 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..cee85ae1508f 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-05 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..219c1b0f1095 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-05 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..8109f1ea544a 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index f88ba52d6af1..ced84972a850 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 554f11d22eb3..ca9339575269 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index 3be213361786..b923ed26434d 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -2802,7 +2802,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; }" ], diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index ef1fb301e167..37a04e66dbac 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 96deb16679bb..0e39d5e823f1 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 7935fdee2a27..18cf4e74e068 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-05 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..1872b9e2c96d 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-05 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..c4b9b1fb7f20 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-05 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..f88cb12f037a 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-05 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..729f208a18b9 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -32,7 +32,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | 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..5f941f2cc7ae 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-05 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) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index a3a41ce841cf..d56c8f5d43ca 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-05 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 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index d294a0240556..bea56ebf8e4a 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-05 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..b8740b3f6577 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-05 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..a69a612752a5 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-05 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..a2cba98a0269 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-05 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..3d1d04c4a146 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-05 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..3d2e419803cd 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-05 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..91eed8ab2278 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-05 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..e06c92bc8bdc 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-05 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..29ec8083a7dc 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-05 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..72ec8f56b3eb 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-05 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..46195d6b21e5 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-05 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..94f1bd66b887 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-05 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..d9100164eeb8 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-05 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..abd46cb4e6bb 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index b44d904cdc0e..da4f4dcc2cc9 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 22fc0e2475cc..38e60563d6ec 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-05 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..4fc3a1f26941 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-05 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..3d1e452447cd 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-05 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..ffd241b28188 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-05 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..e4ba10a59943 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-05 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..def4ceb81ea3 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-05 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..f5ae9cefc817 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-05 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..ea9df66ecb92 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-05 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..b81978547926 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 513017f35056..14b8a2f9bd92 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-05 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..c589ca7acf55 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-05 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..3a43a4315329 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index ee3ba1cdfdac..d8df7cb3f30e 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-10-04 +date: 2022-10-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 76458c52782a..662ddfc0c096 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -8262,6 +8262,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.Agent.outputs", + "type": "Array", + "tags": [], + "label": "outputs", + "description": [], + "signature": [ + "{ api_key_id: string; to_retire_api_key_ids?: { id: string; retired_at: string; }[] | undefined; }[] | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/agent.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.Agent.status", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 0dc50255cea6..97ed812af964 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-05 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 | +| 998 | 3 | 894 | 17 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index b4fff902dfab..b5d9a7f0c4fa 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 9aa86b82df10..501baf7e6ff0 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-05 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..c1b2e8296cdc 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-05 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..787594cb2780 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-05 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..9b5cf1de6cc1 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-05 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..6cbd7d1bc704 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-05 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..4b06101a450c 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-05 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..eef5d014adb6 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-05 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..dc74d335defa 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-05 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..c48356b123bb 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-05 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..0d126e506e04 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-05 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..3b079931a286 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-05 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..08657991c2ba 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-05 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..4990225b6998 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-05 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..5747971cb00f 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-05 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..52e3cd8d3d71 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-05 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..aa9dcaf8e5a8 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-05 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..22e8f5322d11 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-05 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..a587fa5adde3 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-05 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..5680aa7ab909 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-05 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..9217c2db0b33 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-05 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..4feead48e7fa 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-05 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..dee2e3ea3a94 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-05 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..db9cc025d771 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-05 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..7599baf0a2fd 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-05 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..0a442417598a 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-05 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..8d971553826b 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-05 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..4baae348047d 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-05 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..58c92c671a57 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-05 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..e3bbeedaaf3e 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-05 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..7b09cad80caf 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-05 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..dcd942283727 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-05 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..ca8c80539ae4 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-05 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..be187441df10 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-05 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..fde3402d33d1 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-05 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..b402060a03c4 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-05 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..e6e169ed5754 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-05 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..aecc7764088b 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-05 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..031fc8e24e32 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-05 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..039a5742259b 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-05 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..3e4fc51cefe6 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-05 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..fcc1f47368a3 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-05 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..5908ce3a085c 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-05 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..f5daf5f2e748 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-05 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..39eee9c14db2 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-05 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..07033f9df1be 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-05 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..a72958f47ac6 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-05 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..8dba48177fb9 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-05 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..b8e5e03e1a72 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-05 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..4da58098ca88 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-05 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..2018325c894d 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-05 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..0f93aa124486 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-05 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..4482b1e279db 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-05 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..01fe3d220674 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-05 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..9337c921a84d 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-05 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..31238d8edb46 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-05 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..f07140499f25 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-05 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..1603b8e30f71 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-05 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..47ca13abdaf8 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-05 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..bb66821f8f2a 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-05 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..4a4acb83b280 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-05 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..0170fbd0767f 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-05 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..019144200626 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-05 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..dca09e01ece9 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-05 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..69f26abafd87 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-05 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..24968843f60a 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-05 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..55162155f697 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-05 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..292713cb04bb 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-05 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..00a59e0e2454 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-05 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..cd03bcd9fb08 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-05 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..2ac0d725245e 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-05 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..056dc9266a1f 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-05 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..f40c592352e9 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-05 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..11a3e61b73a6 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-05 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..0da72ed909f3 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-05 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..1d48aa892d8d 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-05 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..6bf0de5c79e9 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-05 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..acd208ff29b1 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-05 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..3b9083ed0224 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-05 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..9e6d7159913c 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-05 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..4f89c54cbba5 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-05 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..eca76f978602 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-05 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..b02f2b3a80b7 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-05 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..20edfc0fcb21 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-05 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..c4e2a702ae71 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-05 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..f7323b631488 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-05 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..93e453bd3528 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-05 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..cd64292c7ac1 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-05 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..2ec8b01595c1 --- /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-05 +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..5b1e449c4bd1 --- /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-05 +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..8c6772f5d208 --- /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-05 +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..dc748666814f 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-05 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..3c50670643e2 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-05 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..6812449ae6cb 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-05 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..a3836501fb9f 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-05 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..0626c95396b5 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-05 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..2f622a1cdf77 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-05 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..c9da896e2365 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-05 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..83208af11f64 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-05 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..e94023817708 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-05 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..f6d8de55e930 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-05 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..32ace0b77e78 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-05 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..8efc2882116b 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-05 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..7967d20ecc60 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-05 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..b6e4c05837b0 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-05 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..3124f455cd0e 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-05 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..b637381bbab3 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-05 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..e77c917db42b 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-05 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..fae215630633 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-05 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..1b5d6fa62627 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-05 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..28da4db687f1 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-05 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..2138d1f7af0f 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-05 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..39072586e1ad 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-05 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..2989cf29ffd7 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-05 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..54253f0aa8df 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-05 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..3b428fe0f742 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-05 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..0654c35c6b42 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-05 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..74a7ede56fc5 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-05 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..d813c0ac3c36 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-05 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..c7f4548eed74 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-05 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..0f13016c2916 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-05 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..f4734047a920 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-05 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..f9b76ec4efed 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-05 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..dec813f5a021 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-05 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..2d9f25f95037 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-05 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..9051c8f82bf5 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-05 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..f2dc8b6a06d8 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-05 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..280c79f1e9cb 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-05 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..5cf655af07db 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-05 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..bd8201ff0acc 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-05 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..3d2da5c1c10b 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-05 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..6d8af3ae2256 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-05 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..e041b9e44824 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-05 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..50f12e5bb152 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-05 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..610ff44695f5 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-05 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..b78c5d9cf18b 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-05 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..db339c8f7e56 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-05 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..c475c660d035 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-05 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..dfcf13ebfc85 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-05 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..2abba8e4311c 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-05 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..e2456a2c00d8 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-05 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..9bb082ea3cc1 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-05 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..dab8065fb6af 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-05 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..81755b6cd955 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-05 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..115762085f35 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-05 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..980c34cd56e8 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-05 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..fbff1b20e176 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-05 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..bb6f6197bba8 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-05 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..aa36c20a4a8a 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-05 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..af264e9075b8 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-05 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..8a3b8a4f6e18 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-05 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..7ba1976190fe 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-05 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..bd3ae6f94d5d 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-05 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..5863aaa53cc0 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-05 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..35567a34412c 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-05 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..70def2d37d1c 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-05 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..9300cc8a05f1 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 8c1171396e64..0ab1267bb6f7 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-05 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..676150973883 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-05 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..3fe29918b68f 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-05 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..61c2c8b9c95a 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-05 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..2341a0a7dca2 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-05 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..0033542fcb0f 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-05 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..b588f9bc78bc 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-05 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..c7d8c231294e 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-05 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..17252dff17e6 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-05 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..3927277788a2 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-05 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..998e0dd35a47 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-05 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..e1e8516762c5 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-05 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..d2003862d3a4 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-05 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..3fd437900c0f 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-05 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..2e0a18af1691 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-05 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..e520bcff849a 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-05 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..95a89822660a 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-05 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..abe69da3764d 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-05 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..f361257cb78d 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-05 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..b35bdccf97c3 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-05 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..f33cd5818490 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, @@ -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..0cf11ce01ad0 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-05 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..b5c569c8fdfd 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-05 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..29de7d07c2c8 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-05 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..617c3e7bfbc3 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-05 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..015ce2965495 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 7b7bc93b5731..888217872f50 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index fe461a5b010f..5a6d9c5c312a 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-05 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..9f6b3e808168 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-05 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..d258d85a40bf 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-05 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..3900861f2c75 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-05 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..2d6b86c049e5 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-05 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..6bcbaab77880 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-05 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..2bd00377ab4e 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-05 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..a0d9668e08c3 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-05 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..2b07f557f982 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-05 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..796900c1993c 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-05 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..d3cb22025a19 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-05 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..06eb0be4c6e9 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-05 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..1033277498dc 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-05 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..706f3b412c9f 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-05 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..c0ace53be6aa 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-05 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..c73eb157c378 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-05 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..d2d290fc39b5 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-05 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..750f182c6ef8 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-05 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..04f901658fc4 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-05 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..dc4d2d056859 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-05 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..e5cea6347ce7 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-05 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..2fdb499fca0f 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-05 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..73b7bb5df8cf 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-05 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..2043b5375594 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-05 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..4d5c227a1640 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-05 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..99648371f5af 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-05 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..6cbecbcc5043 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-05 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..10c8c7e687eb 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-05 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..2360b8fb353d 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-05 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..4c74267825fb 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-05 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..34b2c2d08fe6 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-05 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..e773eaf72441 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-05 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..8ef2712421b4 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-05 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..46116743ad8c 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-05 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..8f6421906ff6 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-05 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..d33a8812a184 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-05 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..69a8f7958e81 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-05 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..51f5421b03e8 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-05 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..d3f386924f2d 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-05 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..85a3c3c328e4 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-05 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..d77794d5d25b 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-05 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..5b3005777d04 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-05 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..a40e0f93af4b 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-05 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..a403b0c4f886 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-05 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..2ccd4e130015 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-05 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..fae80cea239e 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 768c6cf059b5..e1430dff27fd 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 3496eef3b634..6bd16b292279 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-05 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..9e05fc653df0 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-05 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..0524cc14c9da 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-05 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..bdccce178feb 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-05 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..c3288ff3462c 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-05 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..0f74b1ed20d1 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-05 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..3680059e0bb3 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-05 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..5d9114b7fcfb 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-05 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..6dcc02bebe7d 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-05 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..0b2e455cd53a 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-05 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..29ab4005784a 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-05 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..97ed5c53aa6a 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-05 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..bf6e90ee0297 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-05 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..0efc3f3002fb 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-05 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..9559da7fcfb8 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-05 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..99aafea368d3 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-05 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..4ca01157d26c 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-05 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..246f16ec7933 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-05 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..5e05cce3b817 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-05 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..fa7c3beb61d7 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-05 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..8f46c57d6979 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-05 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..db0f60967570 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-05 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..30f73a9dcef0 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-05 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..4a0a6c721824 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-05 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..197033077119 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-05 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..ed08b8695873 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-05 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..fe9ba7be266a 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-05 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..7c5d6434bacd 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-05 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..6d5b6b30ea6b 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-05 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..bdbb8f89733e 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-05 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..1f56256dca60 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-05 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..564d494c0182 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-05 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..75218e1b9152 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-05 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..9263c4ae7239 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-05 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..a174ae9aa31d 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-05 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..c4e7d23f9ee8 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-05 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..b615096d6327 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-05 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..5581ffbbe5e4 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-05 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..a87f8c0d8c90 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-05 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..d2410615264b 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-05 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..f2475d6a211a 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-05 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..8e1879e083fd 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-05 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..3587d0729d9b 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-05 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..2012b6d492ce 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-05 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..e836f6a148f7 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-05 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..bf64118f4887 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-05 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..654d05d169d8 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-05 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..4c7692f9641e 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-05 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..aec610de5a90 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-05 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..f5d8e050ab35 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-05 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..902fc381fca9 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-05 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..d4d841a294ec 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-05 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..af1ecf8a2344 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index e930504ffaee..d857708fd3b1 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 14b1e5c6f461..667d7d1f9e97 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-05 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..68df9af402dd 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-05 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..90c4ae29e02b 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 014535d3acb8..3eaa890dd2ef 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-05 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..45768ee0f0e6 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-05 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..f9c4152861ba 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-05 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..82db425319f6 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-05 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..5941b34b4d15 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-05 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..3eb96b9e42de 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-05 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..ead1822bd916 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-05 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..9e694151645c 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-05 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..0846ce9a14b0 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-05 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..710c9036757f 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -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..6f51a9c520db 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-05 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..5150acfd6948 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-05 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..a40f1999b7d1 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,34 +15,37 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 480 | 399 | 38 | +| 486 | 403 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 32043 | 179 | 21580 | 1010 | +| 32135 | 179 | 21634 | 1018 | ## 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) | - | 216 | 0 | 211 | 21 | | | [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 | @@ -81,7 +84,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [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 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 998 | 3 | 894 | 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 | @@ -108,7 +111,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [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,7 +155,7 @@ 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) | - | 514 | 1 | 487 | 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 | @@ -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 | @@ -388,7 +394,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 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 7e399db93218..eefd191f746e 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 7efed272b726..1e04dc70632f 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-05 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..c3a90a8c57d9 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-05 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..c5ffba829e4f 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-05 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..77b244443d8c 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-05 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..d8008df18edf 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-05 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..12b365bcf6e4 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-05 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..4c74b770eccf 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-05 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..55f7dc92e076 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-05 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..3c8f636d7d5b 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-05 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..ab3dfead9871 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-05 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..f35fbaf25ca1 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-05 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..f6f7fc886f12 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-05 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..8cba4be59b07 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-05 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..11bcf322960e 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-05 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..528330493abc 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-05 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..9203fe6baafd 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-05 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..2cd8840a5b05 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-05 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..3cf815ff9941 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-05 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..10c69137d75c 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-05 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..b9066eeb4629 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 7c124256a797..4ff5487016b1 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-05 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..47e5c2cbeb22 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-05 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..29f4ab12cf09 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-05 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..59cc40b520d9 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-05 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..ebe073a2dad0 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-05 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..57d30d39c34f 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-05 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..e6e1e05a79c3 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-05 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..044def849099 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-05 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..81b0b1789f9e 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-05 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..8ee424cb6ead 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-05 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..e7e28e5d542d 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, @@ -8025,6 +8025,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", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 5146401d1a6e..553179f8aed5 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-05 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 | +| 514 | 1 | 487 | 49 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 3997d6297067..39d505237f64 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-05 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..e57f9446d9af 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-05 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..7d965fcc4ddc 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 7ea6ab07192c..a88860acd6c9 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 76e6013841c7..989827e3c006 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index bc3da076b591..c67daad53c29 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-05 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..b32c487d4133 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-05 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..3c2dd48543e5 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-05 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..daeeb706148a 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-05 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..44e60c7dfb71 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-05 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..aece34dc1143 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-05 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..8d3d8bb8e6fd 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-05 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..28ec9d3db8f1 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-05 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..c9936e081013 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-05 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..1dc3162b3cf5 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-05 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..0870b882f64e 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-05 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..f53c14cd2fb3 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-05 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..6e6a3a1e58b5 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 4c4afd430bce..9711621cbf74 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-05 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; 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/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/package.json b/package.json index f122f532d527..ebadbc429e35 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", @@ -215,6 +216,9 @@ "@kbn/core-http-context-server-mocks": "link:bazel-bin/packages/core/http/core-http-context-server-mocks", "@kbn/core-http-request-handler-context-server": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server", "@kbn/core-http-request-handler-context-server-internal": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server-internal", + "@kbn/core-http-resources-server": "link:bazel-bin/packages/core/http/core-http-resources-server", + "@kbn/core-http-resources-server-internal": "link:bazel-bin/packages/core/http/core-http-resources-server-internal", + "@kbn/core-http-resources-server-mocks": "link:bazel-bin/packages/core/http/core-http-resources-server-mocks", "@kbn/core-http-router-server-internal": "link:bazel-bin/packages/core/http/core-http-router-server-internal", "@kbn/core-http-router-server-mocks": "link:bazel-bin/packages/core/http/core-http-router-server-mocks", "@kbn/core-http-server": "link:bazel-bin/packages/core/http/core-http-server", @@ -482,7 +486,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", @@ -687,8 +690,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", @@ -816,7 +819,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", @@ -942,6 +944,9 @@ "@types/kbn__core-http-context-server-mocks": "link:bazel-bin/packages/core/http/core-http-context-server-mocks/npm_module_types", "@types/kbn__core-http-request-handler-context-server": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server/npm_module_types", "@types/kbn__core-http-request-handler-context-server-internal": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server-internal/npm_module_types", + "@types/kbn__core-http-resources-server": "link:bazel-bin/packages/core/http/core-http-resources-server/npm_module_types", + "@types/kbn__core-http-resources-server-internal": "link:bazel-bin/packages/core/http/core-http-resources-server-internal/npm_module_types", + "@types/kbn__core-http-resources-server-mocks": "link:bazel-bin/packages/core/http/core-http-resources-server-mocks/npm_module_types", "@types/kbn__core-http-router-server-internal": "link:bazel-bin/packages/core/http/core-http-router-server-internal/npm_module_types", "@types/kbn__core-http-router-server-mocks": "link:bazel-bin/packages/core/http/core-http-router-server-mocks/npm_module_types", "@types/kbn__core-http-server": "link:bazel-bin/packages/core/http/core-http-server/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 0d5ecd4bc4cf..97b7064f4cd7 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -81,6 +81,9 @@ filegroup( "//packages/core/http/core-http-context-server-mocks:build", "//packages/core/http/core-http-request-handler-context-server:build", "//packages/core/http/core-http-request-handler-context-server-internal:build", + "//packages/core/http/core-http-resources-server:build", + "//packages/core/http/core-http-resources-server-internal:build", + "//packages/core/http/core-http-resources-server-mocks:build", "//packages/core/http/core-http-router-server-internal:build", "//packages/core/http/core-http-router-server-mocks:build", "//packages/core/http/core-http-server:build", @@ -418,6 +421,9 @@ filegroup( "//packages/core/http/core-http-context-server-mocks:build_types", "//packages/core/http/core-http-request-handler-context-server:build_types", "//packages/core/http/core-http-request-handler-context-server-internal:build_types", + "//packages/core/http/core-http-resources-server:build_types", + "//packages/core/http/core-http-resources-server-internal:build_types", + "//packages/core/http/core-http-resources-server-mocks:build_types", "//packages/core/http/core-http-router-server-internal:build_types", "//packages/core/http/core-http-router-server-mocks:build_types", "//packages/core/http/core-http-server:build_types", diff --git a/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts b/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts index ac40eedfccb7..dd750a56fbf2 100644 --- a/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts +++ b/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts @@ -61,6 +61,19 @@ const mockedResponse: StatusResponse = { '15m': 0.1, }, }, + elasticsearch_client: { + protocol: 'https', + connectedNodes: 3, + nodesWithActiveSockets: 3, + nodesWithIdleSockets: 1, + totalActiveSockets: 25, + totalIdleSockets: 2, + totalQueuedRequests: 0, + mostActiveNodeSockets: 15, + averageActiveSocketsPerNode: 8, + mostIdleNodeSockets: 2, + averageIdleSocketsPerNode: 0.5, + }, process: { pid: 1, memory: { diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts index aa1364c179e1..6f1f276c7d08 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts @@ -9,7 +9,7 @@ export { ScopedClusterClient } from './src/scoped_cluster_client'; export { ClusterClient } from './src/cluster_client'; export { configureClient } from './src/configure_client'; -export { AgentManager } from './src/agent_manager'; +export { type AgentStore, AgentManager } from './src/agent_manager'; export { getRequestDebugMeta, getErrorMessage } from './src/log_query_and_deprecation'; export { PRODUCT_RESPONSE_HEADER, 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 811d9d95831e..dfa8a077d2e5 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 @@ -104,10 +104,10 @@ describe('AgentManager', () => { const agentFactory = agentManager.getAgentFactory(); const agent = agentFactory({ url: new URL('http://elastic-node-1:9200') }); // eslint-disable-next-line dot-notation - expect(agentManager['httpStore'].has(agent)).toEqual(true); + expect(agentManager['agents'].has(agent)).toEqual(true); agent.destroy(); // eslint-disable-next-line dot-notation - expect(agentManager['httpStore'].has(agent)).toEqual(false); + expect(agentManager['agents'].has(agent)).toEqual(false); }); }); @@ -122,4 +122,21 @@ describe('AgentManager', () => { }); }); }); + + describe('#getAgents()', () => { + it('returns the created HTTP and HTTPs Agent instances', () => { + const agentManager = new AgentManager(); + const agentFactory1 = agentManager.getAgentFactory(); + const agentFactory2 = agentManager.getAgentFactory(); + const agent1 = agentFactory1({ url: new URL('http://elastic-node-1:9200') }); + const agent2 = agentFactory2({ url: new URL('http://elastic-node-1:9200') }); + const agent3 = agentFactory1({ url: new URL('https://elastic-node-1:9200') }); + const agent4 = agentFactory2({ url: new URL('https://elastic-node-1:9200') }); + + const agents = agentManager.getAgents(); + + expect(agents.size).toEqual(4); + expect([...agents]).toEqual(expect.arrayContaining([agent1, agent2, agent3, agent4])); + }); + }); }); 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 eb68014561d7..9a57cc44e04a 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 @@ -8,7 +8,7 @@ import { Agent as HttpAgent } from 'http'; import { Agent as HttpsAgent } from 'https'; -import { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch'; +import type { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch'; const HTTPS = 'https:'; const DEFAULT_CONFIG: HttpAgentOptions = { @@ -22,6 +22,14 @@ const DEFAULT_CONFIG: HttpAgentOptions = { export type NetworkAgent = HttpAgent | HttpsAgent; export type AgentFactory = (connectionOpts: ConnectionOptions) => NetworkAgent; +export interface AgentFactoryProvider { + getAgentFactory(agentOptions?: HttpAgentOptions): AgentFactory; +} + +export interface AgentStore { + getAgents(): Set; +} + /** * Allows obtaining Agent factories, which can then be fed into elasticsearch-js's Client class. * Ideally, we should obtain one Agent factory for each ES Client class. @@ -33,15 +41,11 @@ export type AgentFactory = (connectionOpts: ConnectionOptions) => NetworkAgent; * exposes methods that can modify the underlying pools, effectively impacting the connections of other Clients. * @internal **/ -export class AgentManager { - // Stores Https Agent instances - private httpsStore: Set; - // Stores Http Agent instances - private httpStore: Set; +export class AgentManager implements AgentFactoryProvider, AgentStore { + private agents: Set; constructor(private agentOptions: HttpAgentOptions = DEFAULT_CONFIG) { - this.httpsStore = new Set(); - this.httpStore = new Set(); + this.agents = new Set(); } public getAgentFactory(agentOptions?: HttpAgentOptions): AgentFactory { @@ -61,8 +65,8 @@ export class AgentManager { connectionOpts.tls ); httpsAgent = new HttpsAgent(config); - this.httpsStore.add(httpsAgent); - dereferenceOnDestroy(this.httpsStore, httpsAgent); + this.agents.add(httpsAgent); + dereferenceOnDestroy(this.agents, httpsAgent); } return httpsAgent; @@ -71,19 +75,23 @@ export class AgentManager { if (!httpAgent) { const config = Object.assign({}, DEFAULT_CONFIG, this.agentOptions, agentOptions); httpAgent = new HttpAgent(config); - this.httpStore.add(httpAgent); - dereferenceOnDestroy(this.httpStore, httpAgent); + this.agents.add(httpAgent); + dereferenceOnDestroy(this.agents, httpAgent); } return httpAgent; }; } + + public getAgents(): Set { + return this.agents; + } } -const dereferenceOnDestroy = (protocolStore: Set, agent: NetworkAgent) => { +const dereferenceOnDestroy = (store: Set, agent: NetworkAgent) => { const doDestroy = agent.destroy.bind(agent); agent.destroy = () => { - protocolStore.delete(agent); + store.delete(agent); doDestroy(); }; }; 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 e5be9fc0ab71..f371e3425b0c 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 @@ -46,7 +46,7 @@ describe('ClusterClient', () => { let authHeaders: ReturnType; let internalClient: jest.Mocked; let scopedClient: jest.Mocked; - let agentManager: AgentManager; + let agentFactoryProvider: AgentManager; const mockTransport = { mockTransport: true }; @@ -54,7 +54,7 @@ describe('ClusterClient', () => { logger = loggingSystemMock.createLogger(); internalClient = createClient(); scopedClient = createClient(); - agentManager = new AgentManager(); + agentFactoryProvider = new AgentManager(); authHeaders = httpServiceMock.createAuthHeaderStorage(); authHeaders.get.mockImplementation(() => ({ @@ -84,21 +84,21 @@ describe('ClusterClient', () => { authHeaders, type: 'custom-type', getExecutionContext: getExecutionContextMock, - agentManager, + agentFactoryProvider, kibanaVersion, }); expect(configureClientMock).toHaveBeenCalledTimes(2); expect(configureClientMock).toHaveBeenCalledWith(config, { logger, - agentManager, + agentFactoryProvider, kibanaVersion, type: 'custom-type', getExecutionContext: getExecutionContextMock, }); expect(configureClientMock).toHaveBeenCalledWith(config, { logger, - agentManager, + agentFactoryProvider, kibanaVersion, type: 'custom-type', getExecutionContext: getExecutionContextMock, @@ -113,7 +113,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -128,7 +128,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -155,7 +155,7 @@ describe('ClusterClient', () => { authHeaders, getExecutionContext, getUnauthorizedErrorHandler, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -179,7 +179,7 @@ describe('ClusterClient', () => { authHeaders, getExecutionContext, getUnauthorizedErrorHandler, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -212,7 +212,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -237,7 +237,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -271,7 +271,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({}); @@ -305,7 +305,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -344,7 +344,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({}); @@ -373,7 +373,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -410,7 +410,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({}); @@ -445,7 +445,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -482,7 +482,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -513,7 +513,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -547,7 +547,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -579,7 +579,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = { @@ -612,7 +612,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = { @@ -640,7 +640,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -658,7 +658,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -703,7 +703,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -720,7 +720,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts index f243c98ecf79..2a2f6ef1334a 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts @@ -24,9 +24,12 @@ import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { configureClient } from './configure_client'; import { ScopedClusterClient } from './scoped_cluster_client'; import { getDefaultHeaders } from './headers'; -import { createInternalErrorHandler, InternalUnauthorizedErrorHandler } from './retry_unauthorized'; +import { + createInternalErrorHandler, + type InternalUnauthorizedErrorHandler, +} from './retry_unauthorized'; import { createTransport } from './create_transport'; -import { AgentManager } from './agent_manager'; +import type { AgentFactoryProvider } from './agent_manager'; const noop = () => undefined; @@ -49,7 +52,7 @@ export class ClusterClient implements ICustomClusterClient { authHeaders, getExecutionContext = noop, getUnauthorizedErrorHandler = noop, - agentManager, + agentFactoryProvider, kibanaVersion, }: { config: ElasticsearchClientConfig; @@ -58,7 +61,7 @@ export class ClusterClient implements ICustomClusterClient { authHeaders?: IAuthHeadersStorage; getExecutionContext?: () => string | undefined; getUnauthorizedErrorHandler?: () => UnauthorizedErrorHandler | undefined; - agentManager: AgentManager; + agentFactoryProvider: AgentFactoryProvider; kibanaVersion: string; }) { this.config = config; @@ -71,7 +74,7 @@ export class ClusterClient implements ICustomClusterClient { logger, type, getExecutionContext, - agentManager, + agentFactoryProvider, kibanaVersion, }); this.rootScopedClient = configureClient(config, { @@ -79,7 +82,7 @@ export class ClusterClient implements ICustomClusterClient { type, getExecutionContext, scoped: true, - agentManager, + agentFactoryProvider, kibanaVersion, }); } diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts index 40824d306ac4..fe511f46278d 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts @@ -10,7 +10,6 @@ jest.mock('./log_query_and_deprecation', () => ({ __esModule: true, instrumentEsQueryAndDeprecationLogger: jest.fn(), })); -jest.mock('./agent_manager'); import { Agent } from 'http'; import { @@ -24,9 +23,8 @@ import { ClusterConnectionPool } from '@elastic/elasticsearch'; import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { configureClient } from './configure_client'; import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecation'; -import { AgentManager } from './agent_manager'; +import { type AgentFactoryProvider, AgentManager } from './agent_manager'; -const AgentManagerMock = AgentManager as jest.Mock; const kibanaVersion = '1.0.0'; const createFakeConfig = ( @@ -46,31 +44,17 @@ const createFakeClient = () => { return client; }; -const createFakeAgentFactory = (logger: MockedLogger) => { - const agentFactory = () => new Agent(); - - AgentManagerMock.mockImplementationOnce(() => { - const agentManager = new AgentManager(); - agentManager.getAgentFactory = () => agentFactory; - return agentManager; - }); - - const agentManager = new AgentManager(); - - return { agentManager, agentFactory }; -}; - describe('configureClient', () => { let logger: MockedLogger; let config: ElasticsearchClientConfig; - let agentManager: AgentManager; + let agentFactoryProvider: AgentFactoryProvider; beforeEach(() => { logger = loggingSystemMock.createLogger(); config = createFakeConfig(); parseClientOptionsMock.mockReturnValue({}); ClientMock.mockImplementation(() => createFakeClient()); - agentManager = new AgentManager(); + agentFactoryProvider = new AgentManager(); }); afterEach(() => { @@ -80,14 +64,26 @@ describe('configureClient', () => { }); it('calls `parseClientOptions` with the correct parameters', () => { - configureClient(config, { logger, type: 'test', scoped: false, agentManager, kibanaVersion }); + configureClient(config, { + logger, + type: 'test', + scoped: false, + agentFactoryProvider, + kibanaVersion, + }); expect(parseClientOptionsMock).toHaveBeenCalledTimes(1); expect(parseClientOptionsMock).toHaveBeenCalledWith(config, false, kibanaVersion); parseClientOptionsMock.mockClear(); - configureClient(config, { logger, type: 'test', scoped: true, agentManager, kibanaVersion }); + configureClient(config, { + logger, + type: 'test', + scoped: true, + agentFactoryProvider, + kibanaVersion, + }); expect(parseClientOptionsMock).toHaveBeenCalledTimes(1); expect(parseClientOptionsMock).toHaveBeenCalledWith(config, true, kibanaVersion); @@ -103,7 +99,7 @@ describe('configureClient', () => { logger, type: 'test', scoped: false, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -112,13 +108,17 @@ describe('configureClient', () => { expect(client).toBe(ClientMock.mock.results[0].value); }); - it('constructs a client using the provided `agentManager`', () => { - const { agentManager: customAgentManager, agentFactory } = createFakeAgentFactory(logger); + it('constructs a client using the provided `agentFactoryProvider`', () => { + const agentFactory = () => new Agent(); + const customAgentFactoryProvider = { + getAgentFactory: () => agentFactory, + }; + const client = configureClient(config, { logger, type: 'test', scoped: false, - agentManager: customAgentManager, + agentFactoryProvider: customAgentFactoryProvider, kibanaVersion, }); @@ -134,7 +134,7 @@ describe('configureClient', () => { type: 'test', scoped: false, getExecutionContext, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -148,7 +148,7 @@ describe('configureClient', () => { type: 'test', scoped: true, getExecutionContext, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -164,7 +164,7 @@ describe('configureClient', () => { logger, type: 'test', scoped: false, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -185,7 +185,7 @@ describe('configureClient', () => { logger, type: 'test', scoped: false, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -203,7 +203,7 @@ describe('configureClient', () => { logger, type: 'test', scoped: false, - agentManager, + agentFactoryProvider, kibanaVersion, }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts index e1c8048c6a89..2fd7a4d4a74b 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts @@ -12,7 +12,7 @@ import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { parseClientOptions } from './client_config'; import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecation'; import { createTransport } from './create_transport'; -import { AgentManager } from './agent_manager'; +import type { AgentFactoryProvider } from './agent_manager'; const noop = () => undefined; @@ -23,14 +23,14 @@ export const configureClient = ( type, scoped = false, getExecutionContext = noop, - agentManager, + agentFactoryProvider, kibanaVersion, }: { logger: Logger; type: string; scoped?: boolean; getExecutionContext?: () => string | undefined; - agentManager: AgentManager; + agentFactoryProvider: AgentFactoryProvider; kibanaVersion: string; } ): Client => { @@ -38,7 +38,7 @@ export const configureClient = ( const KibanaTransport = createTransport({ getExecutionContext }); const client = new Client({ ...clientOptions, - agent: agentManager.getAgentFactory(clientOptions.agent), + agent: agentFactoryProvider.getAgentFactory(clientOptions.agent), Transport: KibanaTransport, Connection: HttpConnection, // using ClusterConnectionPool until https://github.com/elastic/elasticsearch-js/issues/1714 is addressed diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts index c46381d57a7b..0b66d449df01 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts @@ -15,3 +15,4 @@ export type { DeeplyMockedApi, ElasticsearchClientMock, } from './src/mocks'; +export { createAgentStoreMock } from './src/agent_manager.mocks'; diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts new file mode 100644 index 000000000000..2fd8812b3aae --- /dev/null +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.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 type { AgentStore, NetworkAgent } from '@kbn/core-elasticsearch-client-server-internal'; + +export const createAgentStoreMock = (agents: Set = new Set()): AgentStore => ({ + getAgents: jest.fn(() => agents), +}); diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts index cd6d36f0cb11..68a56ff28bc8 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts @@ -6,8 +6,14 @@ * Side Public License, v 1. */ +import type { AgentManager } from '@kbn/core-elasticsearch-client-server-internal'; + export const MockClusterClient = jest.fn(); -export const MockAgentManager = jest.fn(); +export const MockAgentManager: jest.MockedClass = jest.fn().mockReturnValue({ + getAgents: jest.fn(), + getAgentFactory: jest.fn(), +}); + jest.mock('@kbn/core-elasticsearch-client-server-internal', () => ({ ClusterClient: MockClusterClient, AgentManager: MockAgentManager, diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts index 5b54a2c35683..ecd364b4283c 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts @@ -135,7 +135,7 @@ describe('#preboot', () => { ); }); - it('creates a ClusterClient using the internal AgentManager', async () => { + it('creates a ClusterClient using the internal AgentManager as AgentFactoryProvider ', async () => { const prebootContract = await elasticsearchService.preboot(); const customConfig = { keepAlive: true }; const clusterClient = prebootContract.createClient('custom-type', customConfig); @@ -145,7 +145,7 @@ describe('#preboot', () => { expect(MockClusterClient).toHaveBeenCalledTimes(1); expect(MockClusterClient.mock.calls[0][0]).toEqual( // eslint-disable-next-line dot-notation - expect.objectContaining({ agentManager: elasticsearchService['agentManager'] }) + expect.objectContaining({ agentFactoryProvider: elasticsearchService['agentManager'] }) ); }); @@ -201,6 +201,11 @@ describe('#setup', () => { ); }); + it('returns an AgentStore as part of the contract', async () => { + const setupContract = await elasticsearchService.setup(setupDeps); + expect(typeof setupContract.agentStore.getAgents).toEqual('function'); + }); + it('esNodeVersionCompatibility$ only starts polling when subscribed to', async () => { const mockedClient = mockClusterClientInstance.asInternalUser; mockedClient.nodes.info.mockImplementation(() => diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts index f345732c7a7c..fddff8429314 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts @@ -120,6 +120,7 @@ export class ElasticsearchService } this.unauthorizedErrorHandler = handler; }, + agentStore: this.agentManager, }; } @@ -182,7 +183,7 @@ export class ElasticsearchService authHeaders: this.authHeaders, getExecutionContext: () => this.executionContextClient?.getAsHeader(), getUnauthorizedErrorHandler: () => this.unauthorizedErrorHandler, - agentManager: this.agentManager, + agentFactoryProvider: this.agentManager, kibanaVersion: this.kibanaVersion, }); } diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts index 8d05ad0c4cd0..b03b86c7bdd1 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts @@ -12,6 +12,7 @@ import type { ElasticsearchServiceStart, ElasticsearchServiceSetup, } from '@kbn/core-elasticsearch-server'; +import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; import type { ServiceStatus } from '@kbn/core-status-common'; import type { NodesVersionCompatibility, NodeInfo } from './version_check/ensure_es_version'; import type { ClusterInfo } from './get_cluster_info'; @@ -21,6 +22,7 @@ export type InternalElasticsearchServicePreboot = ElasticsearchServicePreboot; /** @internal */ export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceSetup { + agentStore: AgentStore; clusterInfo$: Observable; esNodesCompatibility$: Observable; status$: Observable>; diff --git a/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts b/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts index a1323be0ea71..26d81da24318 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts @@ -13,6 +13,7 @@ import { elasticsearchClientMock, type ClusterClientMock, type CustomClusterClientMock, + createAgentStoreMock, } from '@kbn/core-elasticsearch-client-server-mocks'; import type { ElasticsearchClientConfig, @@ -94,6 +95,7 @@ const createInternalSetupContractMock = () => { level: ServiceStatusLevels.available, summary: 'Elasticsearch is available', }), + agentStore: createAgentStoreMock(), }; return internalSetupContract; }; diff --git a/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts index 57eadf70ef68..a8e065d357ee 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts @@ -7,8 +7,8 @@ */ import type { ElasticsearchClient } from './client'; -import { ScopeableRequest } from './scopeable_request'; -import { IScopedClusterClient } from './scoped_cluster_client'; +import type { ScopeableRequest } from './scopeable_request'; +import type { IScopedClusterClient } from './scoped_cluster_client'; /** * Represents an Elasticsearch cluster API client created by the platform. diff --git a/packages/core/http/core-http-resources-server-internal/BUILD.bazel b/packages/core/http/core-http-resources-server-internal/BUILD.bazel new file mode 100644 index 000000000000..8c286485efaf --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/BUILD.bazel @@ -0,0 +1,115 @@ +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-http-resources-server-internal" +PKG_REQUIRE_NAME = "@kbn/core-http-resources-server-internal" + +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//elastic-apm-node", + "//packages/kbn-logging", + "//packages/kbn-apm-config-loader", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//elastic-apm-node", + "//packages/kbn-apm-config-loader:npm_module_types", + "//packages/kbn-logging:npm_module_types", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/http/core-http-resources-server:npm_module_types", + "//packages/core/rendering/core-rendering-server-internal:npm_module_types", + "//packages/core/http/core-http-request-handler-context-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/http/core-http-resources-server-internal/README.md b/packages/core/http/core-http-resources-server-internal/README.md new file mode 100644 index 000000000000..e1fa72ba6a29 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-resources-server-internal + +This package contains the internal types and implementation for Core's internal `http` resources service. diff --git a/packages/core/http/core-http-resources-server-internal/index.ts b/packages/core/http/core-http-resources-server-internal/index.ts new file mode 100644 index 000000000000..270425ba81f2 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/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 { HttpResourcesService } from './src'; + +export type { InternalHttpResourcesPreboot, InternalHttpResourcesSetup } from './src'; diff --git a/packages/core/http/core-http-resources-server-internal/jest.config.js b/packages/core/http/core-http-resources-server-internal/jest.config.js new file mode 100644 index 000000000000..2b328b864899 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/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/http/core-http-resources-server-internal'], +}; diff --git a/packages/core/http/core-http-resources-server-internal/kibana.jsonc b/packages/core/http/core-http-resources-server-internal/kibana.jsonc new file mode 100644 index 000000000000..bea96a4a6084 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-http-resources-server-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/http/core-http-resources-server-internal/package.json b/packages/core/http/core-http-resources-server-internal/package.json new file mode 100644 index 000000000000..71e4a44a3550 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/core-http-resources-server-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/server/http_resources/get_apm_config.test.mocks.ts b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.mocks.ts similarity index 100% rename from src/core/server/http_resources/get_apm_config.test.mocks.ts rename to packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.mocks.ts diff --git a/src/core/server/http_resources/get_apm_config.test.ts b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.ts similarity index 100% rename from src/core/server/http_resources/get_apm_config.test.ts rename to packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.ts diff --git a/src/core/server/http_resources/get_apm_config.ts b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts similarity index 100% rename from src/core/server/http_resources/get_apm_config.ts rename to packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts diff --git a/src/core/server/http_resources/http_resources_service.test.mocks.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.mocks.ts similarity index 100% rename from src/core/server/http_resources/http_resources_service.test.mocks.ts rename to packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.mocks.ts diff --git a/src/core/server/http_resources/http_resources_service.test.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts similarity index 91% rename from src/core/server/http_resources/http_resources_service.test.ts rename to packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts index ea04f3084750..9bf0de0eca82 100644 --- a/src/core/server/http_resources/http_resources_service.test.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts @@ -12,11 +12,13 @@ import type { RouteConfig } from '@kbn/core-http-server'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { httpServiceMock, httpServerMock } from '@kbn/core-http-server-mocks'; -import { coreMock } from '../mocks'; import { renderingServiceMock } from '@kbn/core-rendering-server-mocks'; import { HttpResourcesService, PrebootDeps, SetupDeps } from './http_resources_service'; -import { httpResourcesMock } from './http_resources_service.mock'; -import { HttpResources } from '..'; +import type { HttpResources } from '@kbn/core-http-resources-server'; +import { + createCoreRequestHandlerContextMock, + createHttpResourcesResponseFactory, +} from './test_helpers/http_resources_service_test_mocks'; const coreContext = mockCoreContext.create(); @@ -26,7 +28,7 @@ describe('HttpResources service', () => { let setupDeps: SetupDeps; let router: ReturnType; const kibanaRequest = httpServerMock.createKibanaRequest(); - const context = coreMock.createCustomRequestHandlerContext({}); + const context = createCoreRequestHandlerContextMock(); const apmConfig = { mockApmConfig: true }; beforeEach(() => { @@ -66,7 +68,7 @@ describe('HttpResources service', () => { }); const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(getDeps().rendering.render).toHaveBeenCalledWith( kibanaRequest, @@ -92,7 +94,7 @@ describe('HttpResources service', () => { const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ @@ -112,7 +114,7 @@ describe('HttpResources service', () => { }); const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(getDeps().rendering.render).toHaveBeenCalledWith( kibanaRequest, @@ -138,7 +140,7 @@ describe('HttpResources service', () => { const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ @@ -159,7 +161,7 @@ describe('HttpResources service', () => { }); const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ body: htmlBody, @@ -186,7 +188,7 @@ describe('HttpResources service', () => { const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ @@ -208,7 +210,7 @@ describe('HttpResources service', () => { }); const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ body: jsBody, @@ -235,7 +237,7 @@ describe('HttpResources service', () => { const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ diff --git a/src/core/server/http_resources/http_resources_service.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts similarity index 96% rename from src/core/server/http_resources/http_resources_service.ts rename to packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts index 7cc88699ea7b..1032611748d3 100644 --- a/src/core/server/http_resources/http_resources_service.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts @@ -23,21 +23,29 @@ import type { InternalRenderingServiceSetup, } from '@kbn/core-rendering-server-internal'; import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; -import { - InternalHttpResourcesSetup, +import type { HttpResources, HttpResourcesResponseOptions, HttpResourcesRenderOptions, HttpResourcesRequestHandler, HttpResourcesServiceToolkit, -} from './types'; +} from '@kbn/core-http-resources-server'; + +import type { InternalHttpResourcesSetup } from './types'; + import { getApmConfig } from './get_apm_config'; +/** + * @internal + */ export interface PrebootDeps { http: InternalHttpServicePreboot; rendering: InternalRenderingServicePreboot; } +/** + * @internal + */ export interface SetupDeps { http: InternalHttpServiceSetup; rendering: InternalRenderingServiceSetup; diff --git a/packages/core/http/core-http-resources-server-internal/src/index.ts b/packages/core/http/core-http-resources-server-internal/src/index.ts new file mode 100644 index 000000000000..b4c1502a92ca --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/src/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 { HttpResourcesService } from './http_resources_service'; + +export type { InternalHttpResourcesPreboot, InternalHttpResourcesSetup } from './types'; diff --git a/packages/core/http/core-http-resources-server-internal/src/test_helpers/http_resources_service_test_mocks.ts b/packages/core/http/core-http-resources-server-internal/src/test_helpers/http_resources_service_test_mocks.ts new file mode 100644 index 000000000000..c9c025135501 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/src/test_helpers/http_resources_service_test_mocks.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; + +// partial duplicate of coreMock +export function createCoreRequestHandlerContextMock() { + return { + core: { + uiSettings: { client: uiSettingsServiceMock.createClient() }, + }, + }; +} + +// duplicate of public mock for internal testing only +export function createHttpResourcesResponseFactory() { + const mocked: jest.Mocked = { + renderCoreApp: jest.fn(), + renderAnonymousCoreApp: jest.fn(), + renderHtml: jest.fn(), + renderJs: jest.fn(), + }; + + return { + ...httpServerMock.createResponseFactory(), + ...mocked, + }; +} diff --git a/packages/core/http/core-http-resources-server-internal/src/types.ts b/packages/core/http/core-http-resources-server-internal/src/types.ts new file mode 100644 index 000000000000..f68520a6387b --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/src/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may 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 { IRouter } from '@kbn/core-http-server'; +import type { HttpResources } from '@kbn/core-http-resources-server'; + +/** + * Allows to configure HTTP response parameters + * @internal + */ +export interface InternalHttpResourcesPreboot { + createRegistrar(router: IRouter): HttpResources; +} + +/** + * Allows to configure HTTP response parameters + * @internal + */ +export type InternalHttpResourcesSetup = InternalHttpResourcesPreboot; diff --git a/packages/core/http/core-http-resources-server-internal/tsconfig.json b/packages/core/http/core-http-resources-server-internal/tsconfig.json new file mode 100644 index 000000000000..71bb40fe57f3 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/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/core/http/core-http-resources-server-mocks/BUILD.bazel b/packages/core/http/core-http-resources-server-mocks/BUILD.bazel new file mode 100644 index 000000000000..81eefd0db2ee --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/BUILD.bazel @@ -0,0 +1,110 @@ +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-http-resources-server-mocks" +PKG_REQUIRE_NAME = "@kbn/core-http-resources-server-mocks" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.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 = [ + "//packages/core/http/core-http-server-mocks", + "//packages/core/http/core-http-resources-server-internal", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-utility-types:npm_module_types", + "//packages/core/http/core-http-server-mocks:npm_module_types", + "//packages/core/http/core-http-resources-server:npm_module_types", + "//packages/core/http/core-http-resources-server-internal: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/http/core-http-resources-server-mocks/README.md b/packages/core/http/core-http-resources-server-mocks/README.md new file mode 100644 index 000000000000..6d9bd3de0dad --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-resources-server-mocks + +This package contains the mocks for Core's internal `http` resources service. diff --git a/packages/core/http/core-http-resources-server-mocks/index.ts b/packages/core/http/core-http-resources-server-mocks/index.ts new file mode 100644 index 000000000000..9b848c2bd32a --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { httpResourcesMock } from './src/http_resources_server.mock'; +export type { HttpResourcesMock } from './src/http_resources_server.mock'; diff --git a/packages/core/http/core-http-resources-server-mocks/jest.config.js b/packages/core/http/core-http-resources-server-mocks/jest.config.js new file mode 100644 index 000000000000..7241454e43f5 --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/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/http/core-http-resources-server-mocks'], +}; diff --git a/packages/core/http/core-http-resources-server-mocks/kibana.jsonc b/packages/core/http/core-http-resources-server-mocks/kibana.jsonc new file mode 100644 index 000000000000..e8703bdd42aa --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-http-resources-server-mocks", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/http/core-http-resources-server-mocks/package.json b/packages/core/http/core-http-resources-server-mocks/package.json new file mode 100644 index 000000000000..47247cd2abaf --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-http-resources-server-mocks", + "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/src/core/server/http_resources/http_resources_service.mock.ts b/packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts similarity index 65% rename from src/core/server/http_resources/http_resources_service.mock.ts rename to packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts index 6c50d720ceab..aec435e65089 100644 --- a/src/core/server/http_resources/http_resources_service.mock.ts +++ b/packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts @@ -7,7 +7,25 @@ */ import { httpServerMock } from '@kbn/core-http-server-mocks'; -import { HttpResources, HttpResourcesServiceToolkit } from './types'; +import type { PublicMethodsOf } from '@kbn/utility-types'; +import { HttpResourcesService } from '@kbn/core-http-resources-server-internal'; +import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; + +export type HttpResourcesMock = jest.Mocked>; + +function createHttpResourcesService() { + const mock: HttpResourcesMock = { + preboot: jest.fn(), + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + + mock.preboot.mockReturnValue(createInternalHttpResourcesPreboot()); + mock.setup.mockReturnValue(createInternalHttpResourcesSetup()); + + return mock; +} const createHttpResourcesMock = (): jest.Mocked => ({ register: jest.fn(), @@ -38,6 +56,7 @@ function createHttpResourcesResponseFactory() { } export const httpResourcesMock = { + create: createHttpResourcesService, createRegistrar: createHttpResourcesMock, createPrebootContract: createInternalHttpResourcesPreboot, createSetupContract: createInternalHttpResourcesSetup, diff --git a/packages/core/http/core-http-resources-server-mocks/tsconfig.json b/packages/core/http/core-http-resources-server-mocks/tsconfig.json new file mode 100644 index 000000000000..71bb40fe57f3 --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/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/core/http/core-http-resources-server/BUILD.bazel b/packages/core/http/core-http-resources-server/BUILD.bazel new file mode 100644 index 000000000000..16583b6801b4 --- /dev/null +++ b/packages/core/http/core-http-resources-server/BUILD.bazel @@ -0,0 +1,107 @@ +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-http-resources-server" +PKG_REQUIRE_NAME = "@kbn/core-http-resources-server" + +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 = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/http/core-http-request-handler-context-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/http/core-http-resources-server/README.md b/packages/core/http/core-http-resources-server/README.md new file mode 100644 index 000000000000..a2f4321f1921 --- /dev/null +++ b/packages/core/http/core-http-resources-server/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-resources-server + +This package contains the public types for Core's HTTP resources service. diff --git a/packages/core/http/core-http-resources-server/index.ts b/packages/core/http/core-http-resources-server/index.ts new file mode 100644 index 000000000000..c4dbd3842b2e --- /dev/null +++ b/packages/core/http/core-http-resources-server/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may 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 { + HttpResourcesRenderOptions, + HttpResourcesResponseOptions, + HttpResourcesServiceToolkit, + HttpResourcesRequestHandler, + HttpResources, +} from './src'; diff --git a/packages/core/http/core-http-resources-server/jest.config.js b/packages/core/http/core-http-resources-server/jest.config.js new file mode 100644 index 000000000000..bfa504fb873d --- /dev/null +++ b/packages/core/http/core-http-resources-server/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/http/core-http-resources-server'], +}; diff --git a/packages/core/http/core-http-resources-server/kibana.jsonc b/packages/core/http/core-http-resources-server/kibana.jsonc new file mode 100644 index 000000000000..a05c1223a781 --- /dev/null +++ b/packages/core/http/core-http-resources-server/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-http-resources-server", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/http/core-http-resources-server/package.json b/packages/core/http/core-http-resources-server/package.json new file mode 100644 index 000000000000..156bc4c8948b --- /dev/null +++ b/packages/core/http/core-http-resources-server/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/core-http-resources-server", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/server/http_resources/index.ts b/packages/core/http/core-http-resources-server/src/index.ts similarity index 80% rename from src/core/server/http_resources/index.ts rename to packages/core/http/core-http-resources-server/src/index.ts index 9148c08a922e..8fbe0e3c1a5e 100644 --- a/src/core/server/http_resources/index.ts +++ b/packages/core/http/core-http-resources-server/src/index.ts @@ -6,14 +6,10 @@ * Side Public License, v 1. */ -export { HttpResourcesService } from './http_resources_service'; - export type { HttpResourcesRenderOptions, HttpResourcesResponseOptions, HttpResourcesServiceToolkit, HttpResourcesRequestHandler, HttpResources, - InternalHttpResourcesPreboot, - InternalHttpResourcesSetup, } from './types'; diff --git a/src/core/server/http_resources/types.ts b/packages/core/http/core-http-resources-server/src/types.ts similarity index 91% rename from src/core/server/http_resources/types.ts rename to packages/core/http/core-http-resources-server/src/types.ts index 246a7394c3ad..6d2e6728e28f 100644 --- a/src/core/server/http_resources/types.ts +++ b/packages/core/http/core-http-resources-server/src/types.ts @@ -7,7 +7,6 @@ */ import type { - IRouter, RouteConfig, IKibanaResponse, ResponseHeaders, @@ -85,20 +84,6 @@ export type HttpResourcesRequestHandler< Context extends RequestHandlerContext = RequestHandlerContext > = RequestHandler; -/** - * Allows to configure HTTP response parameters - * @internal - */ -export interface InternalHttpResourcesPreboot { - createRegistrar(router: IRouter): HttpResources; -} - -/** - * Allows to configure HTTP response parameters - * @internal - */ -export type InternalHttpResourcesSetup = InternalHttpResourcesPreboot; - /** * HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. * Provides API allowing plug-ins to respond with: diff --git a/packages/core/http/core-http-resources-server/tsconfig.json b/packages/core/http/core-http-resources-server/tsconfig.json new file mode 100644 index 000000000000..71bb40fe57f3 --- /dev/null +++ b/packages/core/http/core-http-resources-server/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/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel b/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel index 2b789e97cbe6..9761bcbf1cef 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel +++ b/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel @@ -39,6 +39,8 @@ RUNTIME_DEPS = [ "//packages/kbn-logging", "@npm//moment", "@npm//getos", + ### test dependencies + "//packages/core/elasticsearch/core-elasticsearch-client-server-mocks", ] TYPES_DEPS = [ @@ -50,6 +52,7 @@ TYPES_DEPS = [ "@npm//@types/hapi__hapi", "//packages/kbn-logging:npm_module_types", "//packages/core/metrics/core-metrics-server:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-client-server-internal:npm_module_types", ] jsts_transpiler( diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/index.ts b/packages/core/metrics/core-metrics-collectors-server-internal/index.ts index a4639202353e..351129cdc8ba 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/index.ts +++ b/packages/core/metrics/core-metrics-collectors-server-internal/index.ts @@ -11,3 +11,4 @@ export type { OpsMetricsCollectorOptions } from './src/os'; export { ProcessMetricsCollector } from './src/process'; export { ServerMetricsCollector } from './src/server'; export { EventLoopDelaysMonitor } from './src/event_loop_delays_monitor'; +export { ElasticsearchClientsMetricsCollector } from './src/elasticsearch_client'; diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts new file mode 100644 index 000000000000..363fca6430db --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may 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 { Agent as HttpAgent } from 'http'; +import { Agent as HttpsAgent } from 'https'; +import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks'; +import { createAgentStoreMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { getAgentsSocketsStatsMock } from './get_agents_sockets_stats.test.mocks'; +import { ElasticsearchClientsMetricsCollector } from './elasticsearch_client'; +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; + +jest.mock('@kbn/core-elasticsearch-client-server-internal'); + +describe('ElasticsearchClientsMetricsCollector', () => { + test('#collect calls getAgentsSocketsStats with the Agents managed by the provided AgentManager', async () => { + const agents = new Set([new HttpAgent(), new HttpsAgent()]); + const agentStore = createAgentStoreMock(agents); + getAgentsSocketsStatsMock.mockReturnValueOnce(sampleEsClientMetrics); + + const esClientsMetricsCollector = new ElasticsearchClientsMetricsCollector(agentStore); + const metrics = await esClientsMetricsCollector.collect(); + + expect(agentStore.getAgents).toHaveBeenCalledTimes(1); + expect(getAgentsSocketsStats).toHaveBeenCalledTimes(1); + expect(getAgentsSocketsStats).toHaveBeenNthCalledWith(1, agents); + expect(metrics).toEqual(sampleEsClientMetrics); + }); +}); diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts new file mode 100644 index 000000000000..278fd0218f8c --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ElasticsearchClientsMetrics, MetricsCollector } from '@kbn/core-metrics-server'; +import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; + +export class ElasticsearchClientsMetricsCollector + implements MetricsCollector +{ + constructor(private readonly agentStore: AgentStore) {} + + public async collect(): Promise { + return await getAgentsSocketsStats(this.agentStore.getAgents()); + } + + public reset() { + // we do not have a state in this Collector, aka metrics are not accumulated over time. + // Thus, we don't need to perform any cleanup to reset the collected metrics + } +} diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts new file mode 100644 index 000000000000..4e9688ccc91b --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Agent as HttpAgent } from 'http'; +import { Agent as HttpsAgent } from 'https'; + +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; + +export const getHttpAgentMock = (overrides: Partial) => { + return Object.assign(new HttpAgent(), overrides); +}; + +export const getHttpsAgentMock = (overrides: Partial) => { + return Object.assign(new HttpsAgent(), overrides); +}; + +export const getAgentsSocketsStatsMock: jest.MockedFunction = + jest.fn(); + +jest.doMock('./get_agents_sockets_stats', () => { + return { + getAgentsSocketsStats: getAgentsSocketsStatsMock, + }; +}); diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts new file mode 100644 index 000000000000..513bf2caa854 --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts @@ -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 and the Server Side Public License, v 1; you may 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 { Socket } from 'net'; +import { Agent, IncomingMessage } from 'http'; +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; +import { getHttpAgentMock, getHttpsAgentMock } from './get_agents_sockets_stats.test.mocks'; + +jest.mock('net'); + +const mockSocket = new Socket(); +const mockIncomingMessage = new IncomingMessage(mockSocket); + +describe('getAgentsSocketsStats()', () => { + it('extracts aggregated stats from the specified agents', () => { + const agent1 = getHttpAgentMock({ + sockets: { + node1: [mockSocket, mockSocket, mockSocket], + node2: [mockSocket], + }, + freeSockets: { + node1: [mockSocket], + node3: [mockSocket, mockSocket, mockSocket, mockSocket], + }, + requests: { + node1: [mockIncomingMessage, mockIncomingMessage], + }, + }); + + const agent2 = getHttpAgentMock({ + sockets: { + node1: [mockSocket, mockSocket, mockSocket], + node4: [mockSocket], + }, + freeSockets: { + node3: [mockSocket, mockSocket, mockSocket, mockSocket], + }, + requests: { + node4: [mockIncomingMessage, mockIncomingMessage, mockIncomingMessage, mockIncomingMessage], + }, + }); + + const stats = getAgentsSocketsStats(new Set([agent1, agent2])); + expect(stats).toEqual({ + averageActiveSocketsPerNode: 2.6666666666666665, + averageIdleSocketsPerNode: 4.5, + connectedNodes: 4, + mostActiveNodeSockets: 6, + mostIdleNodeSockets: 8, + nodesWithActiveSockets: 3, + nodesWithIdleSockets: 2, + protocol: 'http', + totalActiveSockets: 8, + totalIdleSockets: 9, + totalQueuedRequests: 6, + }); + }); + + it('takes into account Agent types to determine the `protocol`', () => { + const httpAgent = getHttpAgentMock({ + sockets: { node1: [mockSocket] }, + freeSockets: {}, + requests: {}, + }); + + const httpsAgent = getHttpsAgentMock({ + sockets: { node1: [mockSocket] }, + freeSockets: {}, + requests: {}, + }); + + const noAgents = new Set(); + const httpAgents = new Set([httpAgent, httpAgent]); + const httpsAgents = new Set([httpsAgent, httpsAgent]); + const mixedAgents = new Set([httpAgent, httpsAgent]); + + expect(getAgentsSocketsStats(noAgents).protocol).toEqual('none'); + expect(getAgentsSocketsStats(httpAgents).protocol).toEqual('http'); + expect(getAgentsSocketsStats(httpsAgents).protocol).toEqual('https'); + expect(getAgentsSocketsStats(mixedAgents).protocol).toEqual('mixed'); + }); + + it('does not take into account those Agents that have not had any connection to any node', () => { + const pristineAgentProps = { + sockets: {}, + freeSockets: {}, + requests: {}, + }; + const agent1 = getHttpAgentMock(pristineAgentProps); + const agent2 = getHttpAgentMock(pristineAgentProps); + const agent3 = getHttpAgentMock(pristineAgentProps); + + const stats = getAgentsSocketsStats(new Set([agent1, agent2, agent3])); + + expect(stats).toEqual({ + averageActiveSocketsPerNode: 0, + averageIdleSocketsPerNode: 0, + connectedNodes: 0, + mostActiveNodeSockets: 0, + mostIdleNodeSockets: 0, + nodesWithActiveSockets: 0, + nodesWithIdleSockets: 0, + protocol: 'none', + totalActiveSockets: 0, + totalIdleSockets: 0, + totalQueuedRequests: 0, + }); + }); + + it('takes into account those Agents that have hold mappings to one or more nodes, but that do not currently have any pending requests, active connections or idle connections', () => { + const emptyAgentProps = { + sockets: { + node1: [], + }, + freeSockets: { + node2: [], + }, + requests: { + node3: [], + }, + }; + + const agent1 = getHttpAgentMock(emptyAgentProps); + const agent2 = getHttpAgentMock(emptyAgentProps); + + const stats = getAgentsSocketsStats(new Set([agent1, agent2])); + + expect(stats).toEqual({ + averageActiveSocketsPerNode: 0, + averageIdleSocketsPerNode: 0, + connectedNodes: 3, + mostActiveNodeSockets: 0, + mostIdleNodeSockets: 0, + nodesWithActiveSockets: 0, + nodesWithIdleSockets: 0, + protocol: 'http', + totalActiveSockets: 0, + totalIdleSockets: 0, + totalQueuedRequests: 0, + }); + }); +}); diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts new file mode 100644 index 000000000000..e28c92a56a8a --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.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 and the Server Side Public License, v 1; you may 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 { NetworkAgent } from '@kbn/core-elasticsearch-client-server-internal'; +import { Agent as HttpsAgent } from 'https'; +import { mean } from 'lodash'; +import type { + ElasticsearchClientProtocol, + ElasticsearchClientsMetrics, +} from '@kbn/core-metrics-server'; + +export const getAgentsSocketsStats = (agents: Set): ElasticsearchClientsMetrics => { + const nodes = new Set(); + let totalActiveSockets = 0; + let totalIdleSockets = 0; + let totalQueuedRequests = 0; + let http: boolean = false; + let https: boolean = false; + + const nodesWithActiveSockets: Record = {}; + const nodesWithIdleSockets: Record = {}; + + agents.forEach((agent) => { + const agentRequests = Object.entries(agent.requests) ?? []; + const agentSockets = Object.entries(agent.sockets) ?? []; + const agentFreeSockets = Object.entries(agent.freeSockets) ?? []; + + if (agentRequests.length || agentSockets.length || agentFreeSockets.length) { + if (agent instanceof HttpsAgent) https = true; + else http = true; + + agentRequests.forEach(([node, queue]) => { + nodes.add(node); + totalQueuedRequests += queue?.length ?? 0; + }); + + agentSockets.forEach(([node, sockets]) => { + nodes.add(node); + const activeSockets = sockets?.length ?? 0; + totalActiveSockets += activeSockets; + nodesWithActiveSockets[node] = (nodesWithActiveSockets[node] ?? 0) + activeSockets; + }); + + agentFreeSockets.forEach(([node, freeSockets]) => { + nodes.add(node); + const idleSockets = freeSockets?.length ?? 0; + totalIdleSockets += idleSockets; + nodesWithIdleSockets[node] = (nodesWithIdleSockets[node] ?? 0) + idleSockets; + }); + } + }); + + const activeSocketCounters = Object.values(nodesWithActiveSockets); + const idleSocketCounters = Object.values(nodesWithIdleSockets); + const protocol: ElasticsearchClientProtocol = http + ? https + ? 'mixed' + : 'http' + : https + ? 'https' + : 'none'; + + return { + protocol, + connectedNodes: nodes.size, + nodesWithActiveSockets: activeSocketCounters.filter(Boolean).length, + nodesWithIdleSockets: idleSocketCounters.filter(Boolean).length, + totalActiveSockets, + totalIdleSockets, + totalQueuedRequests, + mostActiveNodeSockets: activeSocketCounters.length ? Math.max(...activeSocketCounters) : 0, + averageActiveSocketsPerNode: activeSocketCounters.length ? mean(activeSocketCounters) : 0, + mostIdleNodeSockets: idleSocketCounters.length ? Math.max(...idleSocketCounters) : 0, + averageIdleSocketsPerNode: idleSocketCounters.length ? mean(idleSocketCounters) : 0, + }; +}; diff --git a/packages/core/metrics/core-metrics-server-internal/BUILD.bazel b/packages/core/metrics/core-metrics-server-internal/BUILD.bazel index da7883016afd..0a7f393ec0b3 100644 --- a/packages/core/metrics/core-metrics-server-internal/BUILD.bazel +++ b/packages/core/metrics/core-metrics-server-internal/BUILD.bazel @@ -37,11 +37,15 @@ NPM_MODULE_EXTRA_FILES = [ RUNTIME_DEPS = [ "@npm//rxjs", "@npm//moment", - "//packages/kbn-logging-mocks", "//packages/kbn-config-schema", - "//packages/core/http/core-http-server-mocks", "//packages/core/metrics/core-metrics-collectors-server-internal", + "//packages/core/elasticsearch/core-elasticsearch-server-internal", + ### test dependencies + "//packages/kbn-logging-mocks", + "//packages/core/http/core-http-server-mocks", + "//packages/core/metrics/core-metrics-server-mocks", "//packages/core/metrics/core-metrics-collectors-server-mocks", + "//packages/core/elasticsearch/core-elasticsearch-server-mocks", ] @@ -57,6 +61,7 @@ TYPES_DEPS = [ "//packages/core/http/core-http-server-internal:npm_module_types", "//packages/core/metrics/core-metrics-server:npm_module_types", "//packages/core/metrics/core-metrics-collectors-server-internal:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server-internal:npm_module_types", ] diff --git a/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts b/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts index 8de7a5fa5dad..d997433667e2 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts @@ -8,6 +8,7 @@ import type { OpsMetrics } from '@kbn/core-metrics-server'; import { getEcsOpsMetricsLog } from './get_ops_metrics_log'; +import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks'; import { collectorMock } from '@kbn/core-metrics-collectors-server-mocks'; function createBaseOpsMetrics(): OpsMetrics { @@ -24,6 +25,7 @@ function createBaseOpsMetrics(): OpsMetrics { memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 }, uptime_in_millis: 1, }, + elasticsearch_client: sampleEsClientMetrics, response_times: { avg_in_millis: 1, max_in_millis: 1 }, requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } }, concurrent_connections: 1, diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts index de78b534b2dc..351e2aca43f5 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts @@ -8,13 +8,15 @@ import moment from 'moment'; +import { take } from 'rxjs/operators'; import { configServiceMock } from '@kbn/config-mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { mockOpsCollector } from './metrics_service.test.mocks'; import { MetricsService } from './metrics_service'; -import { take } from 'rxjs/operators'; +import { OpsMetricsCollector } from './ops_metrics_collector'; const testInterval = 100; @@ -24,6 +26,7 @@ const logger = loggingSystemMock.create(); describe('MetricsService', () => { const httpMock = httpServiceMock.createInternalSetupContract(); + const esServiceMock = elasticsearchServiceMock.createInternalSetup(); let metricsService: MetricsService; beforeEach(() => { @@ -43,9 +46,16 @@ describe('MetricsService', () => { describe('#start', () => { it('invokes setInterval with the configured interval', async () => { - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); await metricsService.start(); + expect(OpsMetricsCollector).toHaveBeenCalledTimes(1); + expect(OpsMetricsCollector).toHaveBeenCalledWith( + httpMock.server, + esServiceMock.agentStore, + expect.objectContaining({ logger: logger.get('metrics') }) + ); + expect(setInterval).toHaveBeenCalledTimes(1); expect(setInterval).toHaveBeenCalledWith(expect.any(Function), testInterval); }); @@ -53,7 +63,7 @@ describe('MetricsService', () => { it('collects the metrics at every interval', async () => { mockOpsCollector.collect.mockResolvedValue(dummyMetrics); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); await metricsService.start(); expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1); @@ -68,7 +78,7 @@ describe('MetricsService', () => { it('resets the collector after each collection', async () => { mockOpsCollector.collect.mockResolvedValue(dummyMetrics); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); // `advanceTimersByTime` only ensure the interval handler is executed @@ -108,7 +118,7 @@ describe('MetricsService', () => { .mockResolvedValueOnce(firstMetrics) .mockResolvedValueOnce(secondMetrics); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); const nextEmission = async () => { @@ -157,7 +167,7 @@ describe('MetricsService', () => { mockOpsCollector.collect .mockResolvedValueOnce(firstMetrics) .mockResolvedValueOnce(secondMetrics); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); const nextEmission = async () => { @@ -176,7 +186,7 @@ describe('MetricsService', () => { it('omits metrics from log message if they are missing or malformed', async () => { const opsLogger = logger.get('metrics', 'ops'); mockOpsCollector.collect.mockResolvedValueOnce({ secondMetrics: 'metrics' }); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); await metricsService.start(); expect(loggingSystemMock.collect(opsLogger).debug[0]).toMatchInlineSnapshot(` Array [ @@ -219,7 +229,7 @@ describe('MetricsService', () => { describe('#stop', () => { it('stops the metrics interval', async () => { - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1); @@ -235,7 +245,7 @@ describe('MetricsService', () => { }); it('completes the metrics observable', async () => { - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); let completed = false; diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts index 8a05b4b57843..95a9dc09bba5 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts @@ -10,6 +10,7 @@ import { firstValueFrom, ReplaySubject } from 'rxjs'; import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import type { InternalElasticsearchServiceSetup } from '@kbn/core-elasticsearch-server-internal'; import type { OpsMetrics, MetricsServiceSetup, @@ -21,6 +22,7 @@ import { getEcsOpsMetricsLog } from './logging'; export interface MetricsServiceSetupDeps { http: InternalHttpServiceSetup; + elasticsearchService: InternalElasticsearchServiceSetup; } /** @internal */ @@ -45,12 +47,15 @@ export class MetricsService this.opsMetricsLogger = coreContext.logger.get('metrics', 'ops'); } - public async setup({ http }: MetricsServiceSetupDeps): Promise { + public async setup({ + http, + elasticsearchService, + }: MetricsServiceSetupDeps): Promise { const config = await firstValueFrom( this.coreContext.configService.atPath(OPS_CONFIG_PATH) ); - this.metricsCollector = new OpsMetricsCollector(http.server, { + this.metricsCollector = new OpsMetricsCollector(http.server, elasticsearchService.agentStore, { logger: this.logger, ...config.cGroupOverrides, }); diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts index b96449fdc2f6..d70753b9f464 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts @@ -11,11 +11,13 @@ import { collectorMock } from '@kbn/core-metrics-collectors-server-mocks'; export const mockOsCollector = collectorMock.create(); export const mockProcessCollector = collectorMock.create(); export const mockServerCollector = collectorMock.create(); +export const mockEsClientCollector = collectorMock.create(); jest.doMock('@kbn/core-metrics-collectors-server-internal', () => { return { OsMetricsCollector: jest.fn().mockImplementation(() => mockOsCollector), ProcessMetricsCollector: jest.fn().mockImplementation(() => mockProcessCollector), ServerMetricsCollector: jest.fn().mockImplementation(() => mockServerCollector), + ElasticsearchClientsMetricsCollector: jest.fn().mockImplementation(() => mockEsClientCollector), }; }); diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts index cd80c35b37f8..87011a663404 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts @@ -8,7 +8,10 @@ import { loggerMock } from '@kbn/logging-mocks'; import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks'; +import { AgentManager } from '@kbn/core-elasticsearch-client-server-internal'; import { + mockEsClientCollector, mockOsCollector, mockProcessCollector, mockServerCollector, @@ -20,7 +23,8 @@ describe('OpsMetricsCollector', () => { beforeEach(() => { const hapiServer = httpServiceMock.createInternalSetupContract().server; - collector = new OpsMetricsCollector(hapiServer, { logger: loggerMock.create() }); + const agentManager = new AgentManager(); + collector = new OpsMetricsCollector(hapiServer, agentManager, { logger: loggerMock.create() }); mockOsCollector.collect.mockResolvedValue('osMetrics'); }); @@ -33,12 +37,14 @@ describe('OpsMetricsCollector', () => { requests: 'serverRequestsMetrics', response_times: 'serverTimingMetrics', }); + mockEsClientCollector.collect.mockResolvedValue(sampleEsClientMetrics); const metrics = await collector.collect(); expect(mockOsCollector.collect).toHaveBeenCalledTimes(1); expect(mockProcessCollector.collect).toHaveBeenCalledTimes(1); expect(mockServerCollector.collect).toHaveBeenCalledTimes(1); + expect(mockEsClientCollector.collect).toHaveBeenCalledTimes(1); expect(metrics).toEqual({ collected_at: expect.any(Date), @@ -47,6 +53,7 @@ describe('OpsMetricsCollector', () => { os: 'osMetrics', requests: 'serverRequestsMetrics', response_times: 'serverTimingMetrics', + elasticsearch_client: sampleEsClientMetrics, }); }); }); diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts index 10958d93c256..8a10f4071b11 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts @@ -8,28 +8,33 @@ import { Server as HapiServer } from '@hapi/hapi'; import type { OpsMetrics, MetricsCollector } from '@kbn/core-metrics-server'; +import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; import { ProcessMetricsCollector, OsMetricsCollector, type OpsMetricsCollectorOptions, ServerMetricsCollector, + ElasticsearchClientsMetricsCollector, } from '@kbn/core-metrics-collectors-server-internal'; export class OpsMetricsCollector implements MetricsCollector { private readonly processCollector: ProcessMetricsCollector; private readonly osCollector: OsMetricsCollector; private readonly serverCollector: ServerMetricsCollector; + private readonly esClientCollector: ElasticsearchClientsMetricsCollector; - constructor(server: HapiServer, opsOptions: OpsMetricsCollectorOptions) { + constructor(server: HapiServer, agentStore: AgentStore, opsOptions: OpsMetricsCollectorOptions) { this.processCollector = new ProcessMetricsCollector(); this.osCollector = new OsMetricsCollector(opsOptions); this.serverCollector = new ServerMetricsCollector(server); + this.esClientCollector = new ElasticsearchClientsMetricsCollector(agentStore); } public async collect(): Promise { - const [processes, os, server] = await Promise.all([ + const [processes, os, esClient, server] = await Promise.all([ this.processCollector.collect(), this.osCollector.collect(), + this.esClientCollector.collect(), this.serverCollector.collect(), ]); @@ -43,6 +48,7 @@ export class OpsMetricsCollector implements MetricsCollector { process: processes[0], processes, os, + elasticsearch_client: esClient, ...server, }; } diff --git a/packages/core/metrics/core-metrics-server-mocks/index.ts b/packages/core/metrics/core-metrics-server-mocks/index.ts index d252b2253243..02d13b8ed5ad 100644 --- a/packages/core/metrics/core-metrics-server-mocks/index.ts +++ b/packages/core/metrics/core-metrics-server-mocks/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { metricsServiceMock } from './src/metrics_service.mock'; +export { metricsServiceMock, sampleEsClientMetrics } from './src/metrics_service.mock'; diff --git a/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts b/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts index 6bbe176ce37e..44601caeaa85 100644 --- a/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts +++ b/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts @@ -17,7 +17,25 @@ import { mocked as eventLoopDelaysMonitorMock, collectorMock, } from '@kbn/core-metrics-collectors-server-mocks'; -import type { MetricsServiceSetup, MetricsServiceStart } from '@kbn/core-metrics-server'; +import type { + ElasticsearchClientsMetrics, + MetricsServiceSetup, + MetricsServiceStart, +} from '@kbn/core-metrics-server'; + +export const sampleEsClientMetrics: ElasticsearchClientsMetrics = { + protocol: 'https', + connectedNodes: 3, + nodesWithActiveSockets: 3, + nodesWithIdleSockets: 1, + totalActiveSockets: 25, + totalIdleSockets: 2, + totalQueuedRequests: 0, + mostActiveNodeSockets: 15, + averageActiveSocketsPerNode: 8, + mostIdleNodeSockets: 2, + averageIdleSocketsPerNode: 0.5, +}; const createInternalSetupContractMock = () => { const setupContract: jest.Mocked = { @@ -39,6 +57,7 @@ const createInternalSetupContractMock = () => { memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 }, uptime_in_millis: 1, }, + elasticsearch_client: sampleEsClientMetrics, response_times: { avg_in_millis: 1, max_in_millis: 1 }, requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } }, concurrent_connections: 1, diff --git a/packages/core/metrics/core-metrics-server/index.ts b/packages/core/metrics/core-metrics-server/index.ts index 51e0b7fe3d95..49bd2a425162 100644 --- a/packages/core/metrics/core-metrics-server/index.ts +++ b/packages/core/metrics/core-metrics-server/index.ts @@ -14,4 +14,6 @@ export type { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics, + ElasticsearchClientProtocol, + ElasticsearchClientsMetrics, } from './src/metrics'; diff --git a/packages/core/metrics/core-metrics-server/src/metrics.ts b/packages/core/metrics/core-metrics-server/src/metrics.ts index dbfa643c8ecc..958f6b75f55e 100644 --- a/packages/core/metrics/core-metrics-server/src/metrics.ts +++ b/packages/core/metrics/core-metrics-server/src/metrics.ts @@ -40,6 +40,44 @@ export interface IntervalHistogram { }; } +/** + * Protocol(s) used by the Elasticsearch Client + * @public + */ + +export type ElasticsearchClientProtocol = 'none' | 'http' | 'https' | 'mixed'; + +/** + * Metrics related to the elasticsearch clients + * @public + */ +export interface ElasticsearchClientsMetrics { + /** The protocol (or protocols) that these Agents are using */ + protocol: ElasticsearchClientProtocol; + /** Number of ES nodes that ES-js client is connecting to */ + connectedNodes: number; + /** Number of nodes with active connections */ + nodesWithActiveSockets: number; + /** Number of nodes with available connections (alive but idle). + * Note that a node can have both active and idle connections at the same time + */ + nodesWithIdleSockets: number; + /** Total number of active sockets (all nodes, all connections) */ + totalActiveSockets: number; + /** Total number of available sockets (alive but idle, all nodes, all connections) */ + totalIdleSockets: number; + /** Total number of queued requests (all nodes, all connections) */ + totalQueuedRequests: number; + /** Number of active connections of the node with most active connections */ + mostActiveNodeSockets: number; + /** Average of active sockets per node (all connections) */ + averageActiveSocketsPerNode: number; + /** Number of idle connections of the node with most idle connections */ + mostIdleNodeSockets: number; + /** Average of available (idle) sockets per node (all connections) */ + averageIdleSocketsPerNode: number; +} + /** * Process related metrics * @public @@ -165,6 +203,10 @@ export interface OpsServerMetrics { export interface OpsMetrics { /** Time metrics were recorded at. */ collected_at: Date; + /** + * Metrics related to the elasticsearch client + */ + elasticsearch_client: ElasticsearchClientsMetrics; /** * Process related metrics. * @deprecated use the processes field instead. diff --git a/packages/core/status/core-status-server-internal/src/routes/status.ts b/packages/core/status/core-status-server-internal/src/routes/status.ts index 34a5a9b4dcd2..199f55159a7c 100644 --- a/packages/core/status/core-status-server-internal/src/routes/status.ts +++ b/packages/core/status/core-status-server-internal/src/routes/status.ts @@ -135,6 +135,7 @@ export const registerStatusRoute = ({ ...lastMetrics.requests, status_codes: lastMetrics.requests.statusCodes, }, + elasticsearch_client: lastMetrics.elasticsearch_client, }, }; diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 445bf9458d45..c4139f6423fa 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -458,6 +458,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..38bf25b0aba7 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -340,6 +340,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..62107ad37327 100644 --- a/packages/kbn-es-query/index.ts +++ b/packages/kbn-es-query/index.ts @@ -67,6 +67,7 @@ export { buildEmptyFilter, buildExistsFilter, buildFilter, + buildOrFilter, buildPhraseFilter, buildPhrasesFilter, buildQueryFilter, @@ -89,6 +90,7 @@ export { isFilterPinned, isFilters, isMatchAllFilter, + isOrFilter, 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..efeb8dd87ee6 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 { handleOrFilter } from './handle_or_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) => handleOrFilter(filter, inputDataViews, options)) .map(cleanFilter) .map(translateToQuery); }; diff --git a/packages/kbn-es-query/src/es_query/handle_or_filter.test.ts b/packages/kbn-es-query/src/es_query/handle_or_filter.test.ts new file mode 100644 index 000000000000..913d61a37848 --- /dev/null +++ b/packages/kbn-es-query/src/es_query/handle_or_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 { handleOrFilter } from './handle_or_filter'; +import { + buildExistsFilter, + buildOrFilter, + buildPhraseFilter, + buildPhrasesFilter, + buildRangeFilter, +} from '../filters'; + +describe('#handleOrFilter', 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 = buildOrFilter([]); + const result = handleOrFilter(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 = buildOrFilter(filters); + const result = handleOrFilter(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 = buildOrFilter(filters); + const result = handleOrFilter(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 OR filters', () => { + const nestedOrFilter = buildOrFilter([ + buildPhraseFilter(getField('machine.os'), 'value', indexPattern), + buildPhraseFilter(getField('extension'), 'value', indexPattern), + ]); + const filters = [ + buildPhraseFilter(getField('extension'), 'value2', indexPattern), + nestedOrFilter, + buildRangeFilter(getField('bytes'), { gte: 10 }, indexPattern), + buildExistsFilter(getField('machine.os.raw'), indexPattern), + ]; + const filter = buildOrFilter(filters); + const result = handleOrFilter(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 = buildOrFilter(filters); + const result = handleOrFilter(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 = buildOrFilter(filters); + const result = handleOrFilter(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), + buildOrFilter([ + 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 = buildOrFilter(filters); + const result = handleOrFilter(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 = buildOrFilter(filters); + const { query, ...rest } = handleOrFilter(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": "OR", + }, + } + `); + }); +}); diff --git a/packages/kbn-es-query/src/es_query/handle_or_filter.ts b/packages/kbn-es-query/src/es_query/handle_or_filter.ts new file mode 100644 index 000000000000..0e74d2981b6f --- /dev/null +++ b/packages/kbn-es-query/src/es_query/handle_or_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, isOrFilter } from '../filters'; +import { DataViewBase } from './types'; +import { buildQueryFromFilters, EsQueryFiltersConfig } from './from_filters'; + +/** @internal */ +export const handleOrFilter = ( + filter: Filter, + inputDataViews?: DataViewBase | DataViewBase[], + options: EsQueryFiltersConfig = {} +): Filter => { + if (!isOrFilter(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/index.ts b/packages/kbn-es-query/src/filters/build_filters/index.ts index d9d4bbb82aeb..5cde5a32fa31 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 './or_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/or_filter.ts b/packages/kbn-es-query/src/filters/build_filters/or_filter.ts new file mode 100644 index 000000000000..e9f1bcb54a6a --- /dev/null +++ b/packages/kbn-es-query/src/filters/build_filters/or_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 OR 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 OrFilterMeta extends FilterMeta { + type: typeof FILTERS.OR; + params: FilterItem[]; +} + +/** + * @public + */ +export interface OrFilter extends Filter { + meta: OrFilterMeta; +} + +/** + * @public + */ +export function isOrFilter(filter: Filter): filter is OrFilter { + return filter?.meta?.type === FILTERS.OR; +} + +/** + * Builds an OR filter. An OR filter is a filter with multiple sub-filters. Each sub-filter (FilterItem) represents a + * condition. + * @param filters An array of OrFilterItem + * @public + */ +export function buildOrFilter(filters: FilterItem[]): OrFilter { + const filter = buildEmptyFilter(false); + return { + ...filter, + meta: { + ...filter.meta, + type: FILTERS.OR, + params: filters, + }, + }; +} 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..03f4556537be 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', + OR = 'OR', } /** diff --git a/packages/kbn-es-query/src/filters/index.ts b/packages/kbn-es-query/src/filters/index.ts index 820559d5f906..1031db259307 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, + buildOrFilter, + isOrFilter, isPhraseFilter, isPhrasesFilter, isRangeFilter, @@ -75,6 +77,9 @@ export type { CustomFilter, RangeFilterParams, QueryStringFilter, + OrFilter, + OrFilterMeta, + 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-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/cli_setup/utils.ts b/src/cli_setup/utils.ts index 5c66fa84c0f3..47b8199f16ea 100644 --- a/src/cli_setup/utils.ts +++ b/src/cli_setup/utils.ts @@ -48,7 +48,7 @@ export const elasticsearch = new ElasticsearchService(logger, kibanaPackageJson. logger, type, // we use an independent AgentManager for cli_setup, no need to track performance of this one - agentManager: new AgentManager(), + agentFactoryProvider: new AgentManager(), kibanaVersion: kibanaPackageJson.version, }); }, diff --git a/src/core/server/core_app/core_app.test.ts b/src/core/server/core_app/core_app.test.ts index 76663eeed2fd..1ea3eeef29a0 100644 --- a/src/core/server/core_app/core_app.test.ts +++ b/src/core/server/core_app/core_app.test.ts @@ -12,7 +12,7 @@ import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { mockRouter } from '@kbn/core-http-router-server-mocks'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import { coreMock, httpServerMock } from '../mocks'; -import { httpResourcesMock } from '../http_resources/http_resources_service.mock'; +import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; import { PluginType } from '../plugins'; import { CoreApp } from './core_app'; import { RequestHandlerContext } from '..'; diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index b8701d7646b7..f0940f6abad5 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -21,7 +21,7 @@ import type { IBasePath, } from '@kbn/core-http-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; -import { HttpResources, HttpResourcesServiceToolkit } from '../http_resources'; +import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; import { InternalCorePreboot, InternalCoreSetup } from '../internal_types'; import { registerBundleRoutes } from './bundle_routes'; import type { InternalCoreAppRequestHandlerContext } from './internal_types'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index dafd53e374fe..6232a17eb611 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -69,7 +69,7 @@ import type { I18nServiceSetup } from '@kbn/core-i18n-server'; import type { StatusServiceSetup } from '@kbn/core-status-server'; import type { UiSettingsServiceSetup, UiSettingsServiceStart } from '@kbn/core-ui-settings-server'; import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; -import { HttpResources } from './http_resources'; +import type { HttpResources } from '@kbn/core-http-resources-server'; import { PluginsServiceSetup, PluginsServiceStart } from './plugins'; export type { PluginOpaqueId } from '@kbn/core-base-common'; @@ -228,7 +228,7 @@ export type { HttpResourcesResponseOptions, HttpResourcesServiceToolkit, HttpResourcesRequestHandler, -} from './http_resources'; +} from '@kbn/core-http-resources-server'; export type { LoggingServiceSetup, diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 683d08fe4f84..c66fdf9a968d 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -59,7 +59,10 @@ import type { InternalUiSettingsServiceStart, } from '@kbn/core-ui-settings-server-internal'; import type { InternalRenderingServiceSetup } from '@kbn/core-rendering-server-internal'; -import { InternalHttpResourcesPreboot, InternalHttpResourcesSetup } from './http_resources'; +import type { + InternalHttpResourcesPreboot, + InternalHttpResourcesSetup, +} from '@kbn/core-http-resources-server-internal'; /** @internal */ export interface InternalCorePreboot { diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index cd0429415e7c..356fd4deb44d 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -32,6 +32,7 @@ import { i18nServiceMock } from '@kbn/core-i18n-server-mocks'; import { statusServiceMock } from '@kbn/core-status-server-mocks'; import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; import { renderingServiceMock } from '@kbn/core-rendering-server-mocks'; +import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; import type { PluginInitializerContext, CoreSetup, @@ -40,7 +41,6 @@ import type { CorePreboot, RequestHandlerContext, } from '.'; -import { httpResourcesMock } from './http_resources/http_resources_service.mock'; import { SharedGlobalConfig } from './plugins'; export { configServiceMock, configDeprecationsMock } from '@kbn/config-mocks'; @@ -48,7 +48,7 @@ export { loggingSystemMock } from '@kbn/core-logging-server-mocks'; export { httpServerMock, sessionStorageMock, httpServiceMock } from '@kbn/core-http-server-mocks'; export { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; export { typeRegistryMock as savedObjectsTypeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -export { httpResourcesMock } from './http_resources/http_resources_service.mock'; +export { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; export { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; export { savedObjectsClientMock, diff --git a/src/core/server/server.ts b/src/core/server/server.ts index b7f41dd31dd0..07e10cc72845 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -68,8 +68,8 @@ import type { } from '@kbn/core-http-request-handler-context-server'; import { RenderingService } from '@kbn/core-rendering-server-internal'; +import { HttpResourcesService } from '@kbn/core-http-resources-server-internal'; import { CoreApp } from './core_app'; -import { HttpResourcesService } from './http_resources'; import { PluginsService, config as pluginsConfig } from './plugins'; import { InternalCorePreboot, InternalCoreSetup, InternalCoreStart } from './internal_types'; import { DiscoveredPlugins } from './plugins'; @@ -280,7 +280,10 @@ export class Server { executionContext: executionContextSetup, }); - const metricsSetup = await this.metrics.setup({ http: httpSetup }); + const metricsSetup = await this.metrics.setup({ + http: httpSetup, + elasticsearchService: elasticsearchServiceSetup, + }); const coreUsageDataSetup = this.coreUsageData.setup({ http: httpSetup, diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index 0f51f5da6235..85720480cf9a 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -20,6 +20,7 @@ import { } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { map$ } from '@kbn/std'; +import { RouteConfigOptions } from '@kbn/core-http-server'; import { StreamingResponseHandler, BatchRequestData, @@ -54,7 +55,8 @@ export interface BfetchServerSetup { context: RequestHandlerContext ) => StreamingResponseHandler, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', - pluginRouter?: ReturnType + pluginRouter?: ReturnType, + options?: RouteConfigOptions<'get' | 'post' | 'put' | 'delete'> ) => void; } @@ -117,14 +119,16 @@ export class BfetchServerPlugin router: ReturnType; logger: Logger; }): BfetchServerSetup['addStreamingResponseRoute'] => - (path, handler, method = 'POST', pluginRouter) => { + (path, handler, method = 'POST', pluginRouter, options) => { const httpRouter = pluginRouter || router; + const routeDefinition = { path: `/${removeLeadingSlash(path)}`, validate: { body: schema.any(), query: schema.object({ compress: schema.boolean({ defaultValue: false }) }), }, + options, }; const routeHandler: RequestHandler = async ( context: RequestHandlerContext, 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/console/public/application/components/settings_modal.tsx b/src/plugins/console/public/application/components/settings_modal.tsx index 095dde1c2950..67b0e2c0d957 100644 --- a/src/plugins/console/public/application/components/settings_modal.tsx +++ b/src/plugins/console/public/application/components/settings_modal.tsx @@ -77,9 +77,9 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { const [polling, setPolling] = useState(props.settings.polling); const [pollInterval, setPollInterval] = useState(props.settings.pollInterval); const [tripleQuotes, setTripleQuotes] = useState(props.settings.tripleQuotes); - const [isHistoryDisabled, setIsHistoryDisabled] = useState(props.settings.isHistoryDisabled); - const [isKeyboardShortcutsDisabled, setIsKeyboardShortcutsDisabled] = useState( - props.settings.isKeyboardShortcutsDisabled + const [isHistoryEnabled, setIsHistoryEnabled] = useState(props.settings.isHistoryEnabled); + const [isKeyboardShortcutsEnabled, setIsKeyboardShortcutsEnabled] = useState( + props.settings.isKeyboardShortcutsEnabled ); const autoCompleteCheckboxes = [ @@ -140,8 +140,8 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { polling, pollInterval, tripleQuotes, - isHistoryDisabled, - isKeyboardShortcutsDisabled, + isHistoryEnabled, + isKeyboardShortcutsEnabled, }); } @@ -153,17 +153,17 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { }, []); const toggleKeyboardShortcuts = useCallback( - (isDisabled: boolean) => { + (isEnabled: boolean) => { if (props.editorInstance) { unregisterCommands(props.editorInstance); - setIsKeyboardShortcutsDisabled(isDisabled); + setIsKeyboardShortcutsEnabled(isEnabled); } }, [props.editorInstance] ); const toggleSavingToHistory = useCallback( - (isDisabled: boolean) => setIsHistoryDisabled(isDisabled), + (isEnabled: boolean) => setIsHistoryEnabled(isEnabled), [] ); @@ -289,11 +289,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { } > } onChange={(e) => toggleSavingToHistory(e.target.checked)} @@ -309,11 +309,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { } > } onChange={(e) => toggleKeyboardShortcuts(e.target.checked)} diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index 74a052646e19..ed8c87b5df14 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -259,8 +259,8 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { }, [settings]); useEffect(() => { - const { isKeyboardShortcutsDisabled } = settings; - if (!isKeyboardShortcutsDisabled) { + const { isKeyboardShortcutsEnabled } = settings; + if (isKeyboardShortcutsEnabled) { registerCommands({ senseEditor: editorInstanceRef.current!, sendCurrentRequest, diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx index 0c7e4c46d95a..e895ddc135db 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx +++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx @@ -106,7 +106,9 @@ describe('useSendCurrentRequest', () => { (sendRequest as jest.Mock).mockReturnValue( [{ request: {} }, { request: {} }] /* two responses to save history */ ); - (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({}); + (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({ + isHistoryEnabled: true, + }); (mockContextValue.services.history.addToHistory as jest.Mock).mockImplementation(() => { // Mock throwing throw new Error('cannot save!'); diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts index 87f72571a63e..28d875c246ca 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts @@ -52,9 +52,9 @@ export const useSendCurrentRequest = () => { const results = await sendRequest({ http, requests }); let saveToHistoryError: undefined | Error; - const { isHistoryDisabled } = settings.toJSON(); + const { isHistoryEnabled } = settings.toJSON(); - if (!isHistoryDisabled) { + if (isHistoryEnabled) { results.forEach(({ request: { path, method, data } }) => { try { history.addToHistory(path, method, data); @@ -84,7 +84,7 @@ export const useSendCurrentRequest = () => { notifications.toasts.remove(toast); }, onDisableSavingToHistory: () => { - settings.setIsHistoryDisabled(true); + settings.setIsHistoryEnabled(false); notifications.toasts.remove(toast); }, }), diff --git a/src/plugins/console/public/services/settings.ts b/src/plugins/console/public/services/settings.ts index aa2280f06064..e4731dd3f3a3 100644 --- a/src/plugins/console/public/services/settings.ts +++ b/src/plugins/console/public/services/settings.ts @@ -15,8 +15,8 @@ export const DEFAULT_SETTINGS = Object.freeze({ tripleQuotes: true, wrapMode: true, autocomplete: Object.freeze({ fields: true, indices: true, templates: true, dataStreams: true }), - isHistoryDisabled: false, - isKeyboardShortcutsDisabled: false, + isHistoryEnabled: true, + isKeyboardShortcutsEnabled: true, }); export interface DevToolsSettings { @@ -31,8 +31,8 @@ export interface DevToolsSettings { polling: boolean; pollInterval: number; tripleQuotes: boolean; - isHistoryDisabled: boolean; - isKeyboardShortcutsDisabled: boolean; + isHistoryEnabled: boolean; + isKeyboardShortcutsEnabled: boolean; } enum SettingKeys { @@ -42,12 +42,32 @@ enum SettingKeys { AUTOCOMPLETE_SETTINGS = 'autocomplete_settings', CONSOLE_POLLING = 'console_polling', POLL_INTERVAL = 'poll_interval', - IS_HISTORY_DISABLED = 'is_history_disabled', - IS_KEYBOARD_SHORTCUTS_DISABLED = 'is_keyboard_shortcuts_disabled', + IS_HISTORY_ENABLED = 'is_history_enabled', + IS_KEYBOARD_SHORTCUTS_ENABLED = 'is_keyboard_shortcuts_enabled', } export class Settings { - constructor(private readonly storage: Storage) {} + constructor(private readonly storage: Storage) { + // Migration from old settings to new ones + this.addMigrationRule('is_history_disabled', SettingKeys.IS_HISTORY_ENABLED, (value: any) => { + return !value; + }); + this.addMigrationRule( + 'is_keyboard_shortcuts_disabled', + SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED, + (value: any) => { + return !value; + } + ); + } + + private addMigrationRule(previousKey: string, newKey: string, migration: (value: any) => any) { + const value = this.storage.get(previousKey); + if (value !== undefined) { + this.storage.set(newKey, migration(value)); + this.storage.delete(previousKey); + } + } getFontSize() { return this.storage.get(SettingKeys.FONT_SIZE, DEFAULT_SETTINGS.fontSize); @@ -94,13 +114,13 @@ export class Settings { return true; } - setIsHistoryDisabled(isDisabled: boolean) { - this.storage.set(SettingKeys.IS_HISTORY_DISABLED, isDisabled); + setIsHistoryEnabled(isEnabled: boolean) { + this.storage.set(SettingKeys.IS_HISTORY_ENABLED, isEnabled); return true; } - getIsHistoryDisabled() { - return this.storage.get(SettingKeys.IS_HISTORY_DISABLED, DEFAULT_SETTINGS.isHistoryDisabled); + getIsHistoryEnabled() { + return this.storage.get(SettingKeys.IS_HISTORY_ENABLED, DEFAULT_SETTINGS.isHistoryEnabled); } setPollInterval(interval: number) { @@ -111,15 +131,15 @@ export class Settings { return this.storage.get(SettingKeys.POLL_INTERVAL, DEFAULT_SETTINGS.pollInterval); } - setIsKeyboardShortcutsDisabled(disable: boolean) { - this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED, disable); + setIsKeyboardShortcutsEnabled(isEnabled: boolean) { + this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED, isEnabled); return true; } getIsKeyboardShortcutsDisabled() { return this.storage.get( - SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED, - DEFAULT_SETTINGS.isKeyboardShortcutsDisabled + SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED, + DEFAULT_SETTINGS.isKeyboardShortcutsEnabled ); } @@ -131,8 +151,8 @@ export class Settings { fontSize: parseFloat(this.getFontSize()), polling: Boolean(this.getPolling()), pollInterval: this.getPollInterval(), - isHistoryDisabled: Boolean(this.getIsHistoryDisabled()), - isKeyboardShortcutsDisabled: Boolean(this.getIsKeyboardShortcutsDisabled()), + isHistoryEnabled: Boolean(this.getIsHistoryEnabled()), + isKeyboardShortcutsEnabled: Boolean(this.getIsKeyboardShortcutsDisabled()), }; } @@ -143,8 +163,8 @@ export class Settings { autocomplete, polling, pollInterval, - isHistoryDisabled, - isKeyboardShortcutsDisabled, + isHistoryEnabled, + isKeyboardShortcutsEnabled, }: DevToolsSettings) { this.setFontSize(fontSize); this.setWrapMode(wrapMode); @@ -152,8 +172,8 @@ export class Settings { this.setAutocomplete(autocomplete); this.setPolling(polling); this.setPollInterval(pollInterval); - this.setIsHistoryDisabled(isHistoryDisabled); - this.setIsKeyboardShortcutsDisabled(isKeyboardShortcutsDisabled); + this.setIsHistoryEnabled(isHistoryEnabled); + this.setIsKeyboardShortcutsEnabled(isKeyboardShortcutsEnabled); } } 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..37a5b2b1efca 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,9 +93,10 @@ const IndexPatternEditorFlyoutContentComponent = ({ requireTimestampField = false, editData, allowAdHoc, + showManagementLink, }: Props) => { const { - services: { http, dataViews, uiSettings, overlays }, + services: { application, http, dataViews, uiSettings, overlays }, } = useKibana(); const { form } = useForm({ @@ -376,6 +385,17 @@ const IndexPatternEditorFlyoutContentComponent = ({

{editData ? editorTitleEditMode : editorTitle}

+ {showManagementLink && editData && editData.id && ( + + {i18n.translate('indexPatternEditor.goToManagementPage', { + defaultMessage: 'View on data view management page', + })} + + )}
{indexPatternTypeSelect} @@ -425,6 +445,7 @@ const IndexPatternEditorFlyoutContentComponent = ({ }} submitDisabled={form.isSubmitted && !form.isValid} isEdit={!!editData} + isPersisted={Boolean(editData && editData.isPersisted())} allowAdHoc={allowAdHoc} /> 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..b74c9acc8b2c 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 }, @@ -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..f832173b7156 100644 --- a/src/plugins/data_view_editor/public/components/footer/footer.tsx +++ b/src/plugins/data_view_editor/public/components/footer/footer.tsx @@ -22,6 +22,7 @@ interface FooterProps { onSubmit: (isAdHoc?: boolean) => void; submitDisabled: boolean; isEdit: boolean; + isPersisted: boolean; allowAdHoc: boolean; } @@ -37,11 +38,25 @@ 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, +}: FooterProps) => { const submitPersisted = () => { onSubmit(false); }; @@ -89,7 +104,11 @@ export const Footer = ({ onCancel, onSubmit, submitDisabled, isEdit, allowAdHoc fill disabled={submitDisabled} > - {isEdit ? editButtonLabel : saveButtonLabel} + {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_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/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/constants/guides_config/security.ts b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts index df17d00d7f2d..769d95f978ed 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', diff --git a/src/plugins/guided_onboarding/public/mocks.tsx b/src/plugins/guided_onboarding/public/mocks.tsx new file mode 100644 index 000000000000..dff111fba67c --- /dev/null +++ b/src/plugins/guided_onboarding/public/mocks.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may 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(), + }, +}; + +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..fad2a01d7989 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); }); }); @@ -87,14 +75,14 @@ describe('GuidedOnboarding ApiService', () => { 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 +96,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 +118,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 +158,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 +209,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 +234,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 +242,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 +266,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 +288,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 +307,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..9ffe13bbbb63 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); @@ -102,8 +108,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( @@ -150,7 +156,7 @@ export class ApiService { * 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 +306,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..dbc300bf0d43 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,34 @@ 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>; +} + export interface StepConfig { id: GuideStepIds; title: string; @@ -34,6 +63,7 @@ export interface StepConfig { path: string; }; status?: StepStatus; + integration?: string; } export interface GuideConfig { title: 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 { 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)) @@ -193,11 +194,21 @@ export function ChangeDataView({ key="manage" icon="indexSettings" data-test-subj="indexPattern-manage-field" - onClick={() => { + onClick={async () => { + 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); - application.navigateToApp('management', { - path: `/kibana/indexPatterns/patterns/${currentDataViewId}`, - }); }} > {i18n.translate('unifiedSearch.query.queryBar.indexPattern.manageFieldButton', { 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/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..bc665edfbbf1 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 { buildOrFilter, isOrFilter } from '@kbn/es-query'; +import { ConditionTypes, getConditionalOperationType } from '../utils'; import type { Operator } from '../filter_bar/filter_editor'; const PATH_SEPARATOR = '.'; 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/index.ts b/src/plugins/unified_search/public/utils/index.ts index 395304c48a91..def500dcc66e 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 './or_filter'; diff --git a/src/plugins/unified_search/public/utils/or_filter.ts b/src/plugins/unified_search/public/utils/or_filter.ts index 419e4f04d74a..f336790947d7 100644 --- a/src/plugins/unified_search/public/utils/or_filter.ts +++ b/src/plugins/unified_search/public/utils/or_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 { isOrFilter, 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 @@ -31,17 +24,3 @@ export const getConditionalOperationType = (filter: FilterItem) => { 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/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/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts index bc04d60c3fb5..7b21a5637d16 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts @@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardAddPanel = getService('dashboardAddPanel'); const queryBar = getService('queryBar'); - describe('Loaded Dashboard', () => { + // Failing: See https://github.com/elastic/kibana/issues/142548 + describe.skip('Loaded Dashboard', () => { let fromTimestamp: string | undefined; const getEvents = async (count: number, options?: GetEventsOptions) => 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/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/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/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts index a6b68d907cb4..b1af4a843b49 100644 --- a/x-pack/plugins/actions/server/actions_config.test.ts +++ b/x-pack/plugins/actions/server/actions_config.test.ts @@ -43,6 +43,15 @@ const defaultActionsConfig: ActionsConfig = { }; describe('ensureUriAllowed', () => { + test('throws an error when the Uri is an empty string', () => { + const config: ActionsConfig = defaultActionsConfig; + expect(() => + getActionsConfigurationUtilities(config).ensureUriAllowed('') + ).toThrowErrorMatchingInlineSnapshot( + `"target url \\"\\" is not added to the Kibana config xpack.actions.allowedHosts"` + ); + }); + test('returns true when "any" hostnames are allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index 7f9b45c368e9..1c7a66978ffb 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -139,3 +139,5 @@ export const config: PluginConfigDescriptor = { }, ], }; + +export { urlAllowListValidator } from './sub_action_framework/helpers'; 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 90951692f545..7c2ab0755a0a 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/README.md +++ b/x-pack/plugins/actions/server/sub_action_framework/README.md @@ -6,6 +6,7 @@ The Kibana actions plugin provides a framework to create executable actions that - Register a sub action and map it to a function of your choice. - Define a schema for the parameters of your sub action. +- Define custom validators (or use the provided helpers) for the parameters of your sub action. - Define a response schema for responses from external services. - Create connectors that are supported by the Cases management system. @@ -353,4 +354,19 @@ plugins.actions.registerSubActionConnectorType({ }); ``` -You can see a full example in [x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts](../../../../test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts) \ No newline at end of file +You can see a full example in [x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts](../../../../test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts) + +### Example: Register sub action connector with custom validators + +The sub actions framework allows custom validators during registration of the connector type. Below is an example of including the URL validation for the `TestSubActionConnector` `url` configuration field. + +```typescript +plugins.actions.registerSubActionConnectorType({ + id: '.test-sub-action-connector', + name: 'Test: Sub action connector', + minimumLicenseRequired: 'platinum' as const, + schema: { config: TestConfigSchema, secrets: TestSecretsSchema }, + validators: [{type: ValidatorType.CONFIG, validate: urlAllowListValidator('url')}] + Service: TestSubActionConnector, +}); +``` diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts b/x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts similarity index 65% rename from x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts rename to x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts index 553cf2edde84..c69caff6b0c7 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases/servicenow/index.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts @@ -5,8 +5,4 @@ * 2.0. */ -export { - getServiceNowITSMConnectorType, - getServiceNowSIRConnectorType, - getServiceNowITOMConnectorType, -} from './servicenow'; +export { urlAllowListValidator } from './validators'; diff --git a/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts b/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts new file mode 100644 index 000000000000..7618fef0f3ea --- /dev/null +++ b/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.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 { i18n } from '@kbn/i18n'; +import { get } from 'lodash'; +import { ValidatorServices } from '../../types'; + +export const urlAllowListValidator = (urlKey: string) => { + return (obj: T, validatorServices: ValidatorServices) => { + const { configurationUtilities } = validatorServices; + try { + const url = get(obj, urlKey, ''); + + configurationUtilities.ensureUriAllowed(url); + } catch (allowListError) { + throw new Error( + i18n.translate('xpack.actions.subActionsFramework.urlValidationError', { + defaultMessage: 'error validating url: {message}', + values: { + message: allowListError.message, + }, + }) + ); + } + }; +}; 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 cdc05524cf84..f584d73d2444 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/types.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/types.ts @@ -10,7 +10,7 @@ import { Logger } from '@kbn/logging'; import type { LicenseType } from '@kbn/licensing-plugin/common/types'; import { ActionsConfigurationUtilities } from '../actions_config'; -import { ActionTypeParams, Services } from '../types'; +import { ActionTypeParams, Services, ValidatorType as ValidationSchema } from '../types'; import { SubActionConnector } from './sub_action_connector'; export interface ServiceParams { @@ -34,6 +34,29 @@ export type IServiceAbstract = abstract new ( params: ServiceParams ) => SubActionConnector; +export enum ValidatorType { + CONFIG, + SECRETS, +} + +interface Validate { + validator: ValidateFn; +} + +export type ValidateFn = NonNullable['customValidator']>; + +interface ConfigValidator extends Validate { + type: ValidatorType.CONFIG; +} + +interface SecretsValidator extends Validate { + type: ValidatorType.SECRETS; +} + +export type Validators = Array< + ConfigValidator | SecretsValidator +>; + export interface SubActionConnectorType { id: string; name: string; @@ -43,6 +66,7 @@ export interface SubActionConnectorType { config: Type; secrets: Type; }; + validators?: Array | SecretsValidator>; Service: IService; } diff --git a/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts b/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts index 6cae35141b49..b28adc0b545b 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts @@ -14,7 +14,7 @@ import { TestSecrets, TestSubActionConnector, } from './mocks'; -import { IService } from './types'; +import { IService, SubActionConnectorType, ValidatorType } from './types'; import { buildValidators } from './validators'; describe('Validators', () => { @@ -36,6 +36,39 @@ describe('Validators', () => { return buildValidators({ configurationUtilities: mockedActionsConfig, connector }); }; + const createValidatorWithCustomValidation = (Service: IService) => { + const configValidator = jest.fn(); + const secretsValidator = jest.fn(); + + const connector: SubActionConnectorType = { + id: '.test', + name: 'Test', + minimumLicenseRequired: 'basic' as const, + supportedFeatureIds: ['alerting'], + schema: { + config: TestConfigSchema, + secrets: TestSecretsSchema, + }, + validators: [ + { + type: ValidatorType.CONFIG, + validator: configValidator, + }, + { + type: ValidatorType.SECRETS, + validator: secretsValidator, + }, + ], + Service, + }; + + return { + validators: buildValidators({ configurationUtilities: mockedActionsConfig, connector }), + configValidator, + secretsValidator, + }; + }; + beforeEach(() => { jest.resetAllMocks(); jest.clearAllMocks(); @@ -96,4 +129,28 @@ describe('Validators', () => { const { params } = validator; expect(() => params.schema.validate({ subAction, subActionParams: {} })).toThrow(); }); + + it('calls the config and secrets custom validator functions', () => { + const validator = createValidatorWithCustomValidation(TestSubActionConnector); + + validator.validators.config.customValidator?.( + { url: 'http://www.example.com' }, + { configurationUtilities: mockedActionsConfig } + ); + + validator.validators.secrets.customValidator?.( + { password: '123', username: 'sam' }, + { configurationUtilities: mockedActionsConfig } + ); + + expect(validator.configValidator).toHaveBeenCalledWith( + { url: 'http://www.example.com' }, + expect.anything() + ); + + expect(validator.secretsValidator).toHaveBeenCalledWith( + { password: '123', username: 'sam' }, + expect.anything() + ); + }); }); diff --git a/x-pack/plugins/actions/server/sub_action_framework/validators.ts b/x-pack/plugins/actions/server/sub_action_framework/validators.ts index be6dafed2816..e9cbbb3ae8f8 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/validators.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/validators.ts @@ -7,8 +7,8 @@ import { schema } from '@kbn/config-schema'; import { ActionsConfigurationUtilities } from '../actions_config'; -import { ActionTypeConfig, ActionTypeSecrets } from '../types'; -import { SubActionConnectorType } from './types'; +import { ActionTypeConfig, ActionTypeSecrets, ValidatorServices } from '../types'; +import { SubActionConnectorType, ValidateFn, Validators, ValidatorType } from './types'; export const buildValidators = < Config extends ActionTypeConfig, @@ -20,12 +20,16 @@ export const buildValidators = < configurationUtilities: ActionsConfigurationUtilities; connector: SubActionConnectorType; }) => { + const { config, secrets } = buildCustomValidators(connector.validators); + return { config: { schema: connector.schema.config, + customValidator: config, }, secrets: { schema: connector.schema.secrets, + customValidator: secrets, }, params: { schema: schema.object({ @@ -42,3 +46,35 @@ export const buildValidators = < }, }; }; + +const buildCustomValidators = (validators?: Validators) => { + const partitionedValidators: { + config: Array>; + secrets: Array>; + } = { config: [], secrets: [] }; + + for (const validatorInfo of validators ?? []) { + if (validatorInfo.type === ValidatorType.CONFIG) { + partitionedValidators.config.push(validatorInfo.validator); + } else { + partitionedValidators.secrets.push(validatorInfo.validator); + } + } + + return { + config: createCustomValidatorFunction(partitionedValidators.config), + secrets: createCustomValidatorFunction(partitionedValidators.secrets), + }; +}; + +const createCustomValidatorFunction = (validators: Array>) => { + if (validators.length <= 0) { + return; + } + + return (value: T, validatorServices: ValidatorServices) => { + for (const validate of validators) { + validate(value, validatorServices); + } + }; +}; diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index ae344d4f62db..c92761ad0a28 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -91,7 +91,7 @@ export type ExecutorType = ( options: ActionTypeExecutorOptions ) => Promise>; -interface ValidatorType { +export interface ValidatorType { schema: { validate(value: unknown): Type; }; diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts index cde747f9272f..b3576c0c5ed4 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.ts +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.ts @@ -75,7 +75,7 @@ export const resolveRuleRoute = ( verifyAccessAndContext(licenseState, async function (context, req, res) { const rulesClient = (await context.alerting).getRulesClient(); const { id } = req.params; - const rule = await rulesClient.resolve({ id }); + const rule = await rulesClient.resolve({ id, includeSnoozeData: true }); return res.ok({ body: rewriteBodyRes(rule), }); diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 7374868a11db..d08f12f054a5 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -715,9 +715,11 @@ export class RulesClient { public async resolve({ id, includeLegacyId, + includeSnoozeData = false, }: { id: string; includeLegacyId?: boolean; + includeSnoozeData?: boolean; }): Promise> { const { saved_object: result, ...resolveResponse } = await this.unsecuredSavedObjectsClient.resolve('alert', id); @@ -750,7 +752,9 @@ export class RulesClient { result.attributes.alertTypeId, result.attributes, result.references, - includeLegacyId + includeLegacyId, + false, + includeSnoozeData ); return { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 297c4b6d60fc..b4a48be3a37f 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -201,6 +201,58 @@ describe('resolve()', () => { `); }); + test('calls saved objects client with id and includeSnoozeData params', async () => { + const rulesClient = new RulesClient(rulesClientParams); + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + id: '1', + type: 'alert', + attributes: { + legacyId: 'some-legacy-id', + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + snoozeSchedule: [ + { + duration: 10000, + rRule: { + dtstart: new Date().toISOString(), + tzid: 'UTC', + count: 1, + }, + }, + ], + muteAll: false, + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + const result = await rulesClient.resolve({ id: '1', includeSnoozeData: true }); + expect(result.isSnoozedUntil).toBeTruthy(); + }); + test('should call useSavedObjectReferences.injectReferences if defined for rule type', async () => { const injectReferencesFn = jest.fn().mockReturnValue({ bar: true, 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/feature_flag/comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts index d1159efd0fc9..7d40105db192 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts @@ -36,19 +36,19 @@ describe('Comparison feature flag', () => { it('shows the comparison feature enabled in services overview', () => { cy.visitKibana('/app/apm/services'); cy.get('input[type="checkbox"]#comparison').should('be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled'); + cy.getByTestSubj('comparisonSelect').should('not.be.disabled'); }); it('shows the comparison feature enabled in dependencies overview', () => { cy.visitKibana('/app/apm/dependencies'); cy.get('input[type="checkbox"]#comparison').should('be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled'); + cy.getByTestSubj('comparisonSelect').should('not.be.disabled'); }); it('shows the comparison feature disabled in service map overview page', () => { cy.visitKibana('/app/apm/service-map'); cy.get('input[type="checkbox"]#comparison').should('be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled'); + cy.getByTestSubj('comparisonSelect').should('not.be.disabled'); }); }); @@ -71,7 +71,7 @@ describe('Comparison feature flag', () => { it('shows the comparison feature disabled in services overview', () => { cy.visitKibana('/app/apm/services'); cy.get('input[type="checkbox"]#comparison').should('not.be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); }); it('shows the comparison feature disabled in dependencies overview page', () => { @@ -81,13 +81,13 @@ describe('Comparison feature flag', () => { cy.visitKibana('/app/apm/dependencies'); cy.wait('@topDependenciesRequest'); cy.get('input[type="checkbox"]#comparison').should('not.be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); }); it('shows the comparison feature disabled in service map overview page', () => { cy.visitKibana('/app/apm/service-map'); cy.get('input[type="checkbox"]#comparison').should('not.be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); }); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts index c25e6a680031..5d275770e462 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts @@ -60,21 +60,19 @@ describe('when navigating to integration page', () => { cy.visitKibana(integrationsPath); // open integration policy form - cy.get('[data-test-subj="integration-card:epr:apm:featured').click(); + cy.getByTestSubj('integration-card:epr:apm:featured').click(); cy.contains('Elastic APM in Fleet').click(); cy.contains('a', 'APM integration').click(); - cy.get('[data-test-subj="addIntegrationPolicyButton"]').click(); + cy.getByTestSubj('addIntegrationPolicyButton').click(); }); it('checks validators for required fields', () => { const requiredFields = policyFormFields.filter((field) => field.required); requiredFields.map((field) => { - cy.get(`[data-test-subj="${field.selector}"`).clear(); - cy.get('[data-test-subj="createPackagePolicySaveButton"').should( - 'be.disabled' - ); - cy.get(`[data-test-subj="${field.selector}"`).type(field.value); + cy.getByTestSubj(field.selector).clear(); + cy.getByTestSubj('createPackagePolicySaveButton').should('be.disabled'); + cy.getByTestSubj(field.selector).type(field.value); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts index 5be39b4f082d..47f8c537b100 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts @@ -90,7 +90,7 @@ describe('Agent configuration', () => { '/api/apm/settings/agent-configuration/environments?*' ).as('serviceEnvironmentApi'); cy.contains('Create configuration').click(); - cy.get('[data-test-subj="serviceNameComboBox"]') + cy.getByTestSubj('serviceNameComboBox') .click() .type('opbeans-node') .type('{enter}'); @@ -98,7 +98,7 @@ describe('Agent configuration', () => { cy.contains('opbeans-node').realClick(); cy.wait('@serviceEnvironmentApi'); - cy.get('[data-test-subj="serviceEnviromentComboBox"]') + cy.getByTestSubj('serviceEnviromentComboBox') .click({ force: true }) .type('prod') .type('{enter}'); @@ -115,14 +115,11 @@ describe('Agent configuration', () => { '/api/apm/settings/agent-configuration/environments' ).as('serviceEnvironmentApi'); cy.contains('Create configuration').click(); - cy.get('[data-test-subj="serviceNameComboBox"]') - .click() - .type('All') - .type('{enter}'); + cy.getByTestSubj('serviceNameComboBox').click().type('All').type('{enter}'); cy.contains('All').realClick(); cy.wait('@serviceEnvironmentApi'); - cy.get('[data-test-subj="serviceEnviromentComboBox"]') + cy.getByTestSubj('serviceEnviromentComboBox') .click({ force: true }) .type('All'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts index 615ff2b49a85..b680f745609b 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts @@ -52,7 +52,7 @@ describe('Custom links', () => { it('creates custom link', () => { cy.visitKibana(basePath); - const emptyPrompt = cy.get('[data-test-subj="customLinksEmptyPrompt"]'); + const emptyPrompt = cy.getByTestSubj('customLinksEmptyPrompt'); cy.contains('Create custom link').click(); cy.contains('Create link'); cy.contains('Save').should('be.disabled'); @@ -63,7 +63,7 @@ describe('Custom links', () => { emptyPrompt.should('not.exist'); cy.contains('foo'); cy.contains('https://foo.com'); - cy.get('[data-test-subj="editCustomLink"]').click(); + cy.getByTestSubj('editCustomLink').click(); cy.contains('Delete').click(); }); @@ -71,14 +71,14 @@ describe('Custom links', () => { cy.visitKibana(basePath); // wait for empty prompt - cy.get('[data-test-subj="customLinksEmptyPrompt"]').should('be.visible'); + cy.getByTestSubj('customLinksEmptyPrompt').should('be.visible'); cy.contains('Create custom link').click(); - cy.get('[data-test-subj="filter-0"]').select('service.name'); + cy.getByTestSubj('filter-0').select('service.name'); cy.get( '[data-test-subj="service.name.value"] [data-test-subj="comboBoxSearchInput"]' ).type('foo'); - cy.get('[data-test-subj="filter-0"]').select('service.environment'); + cy.getByTestSubj('filter-0').select('service.environment'); cy.get( '[data-test-subj="service.environment.value"] [data-test-subj="comboBoxInput"]' ).should('not.contain', 'foo'); 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 e989ea5cf0fa..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 @@ -85,18 +85,31 @@ describe('Storage Explorer', () => { }); it('renders the storage timeseries chart', () => { - cy.get('[data-test-subj="storageExplorerTimeseriesChart"]'); + cy.getByTestSubj('storageExplorerTimeseriesChart'); }); 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'); }); @@ -115,7 +128,7 @@ describe('Storage Explorer', () => { it('with the correct environment when changing the environment', () => { cy.wait(mainAliasNames); - cy.get('[data-test-subj="environmentFilter"]').type('production'); + cy.getByTestSubj('environmentFilter').type('production'); cy.contains('button', 'production').click({ force: true }); @@ -148,7 +161,7 @@ describe('Storage Explorer', () => { it('with the correct lifecycle phase when changing the lifecycle phase', () => { cy.wait(mainAliasNames); - cy.get('[data-test-subj="storageExplorerLifecyclePhaseSelect"]').click(); + cy.getByTestSubj('storageExplorerLifecyclePhaseSelect').click(); cy.contains('button', 'Warm').click(); cy.expectAPIsToHaveBeenCalledWith({ @@ -180,13 +193,13 @@ describe('Storage Explorer', () => { cy.wait(mainAliasNames); cy.contains('opbeans-node'); - cy.get('[data-test-subj="storageDetailsButton_opbeans-node"]').click(); - cy.get('[data-test-subj="loadingSpinner"]').should('be.visible'); + cy.getByTestSubj('storageDetailsButton_opbeans-node').click(); + cy.getByTestSubj('loadingSpinner').should('be.visible'); cy.wait('@storageDetailsRequest'); cy.contains('Service storage details'); - cy.get('[data-test-subj="storageExplorerTimeseriesChart"]'); - cy.get('[data-test-subj="serviceStorageDetailsTable"]'); + cy.getByTestSubj('storageExplorerTimeseriesChart'); + cy.getByTestSubj('serviceStorageDetailsTable'); }); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts index cfcabe85b5b2..00b842f3265c 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts @@ -11,7 +11,7 @@ describe('APM deep links', () => { }); it('navigates to apm links on search elastic', () => { cy.visitKibana('/'); - cy.get('[data-test-subj="nav-search-input"]').type('APM'); + cy.getByTestSubj('nav-search-input').type('APM'); cy.contains('APM'); cy.contains('APM / Services'); cy.contains('APM / Traces'); @@ -23,17 +23,17 @@ describe('APM deep links', () => { cy.contains('APM').click({ force: true }); cy.url().should('include', '/apm/services'); - cy.get('[data-test-subj="nav-search-input"]').type('APM'); + cy.getByTestSubj('nav-search-input').type('APM'); // navigates to services page cy.contains('APM / Services').click({ force: true }); cy.url().should('include', '/apm/services'); - cy.get('[data-test-subj="nav-search-input"]').type('APM'); + cy.getByTestSubj('nav-search-input').type('APM'); // navigates to traces page cy.contains('APM / Traces').click({ force: true }); cy.url().should('include', '/apm/traces'); - cy.get('[data-test-subj="nav-search-input"]').type('APM'); + cy.getByTestSubj('nav-search-input').type('APM'); // navigates to service maps cy.contains('APM / Service Map').click({ force: true }); cy.url().should('include', '/apm/service-map'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts index 653809a8e04d..2ef3ae42b1aa 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts @@ -66,9 +66,9 @@ describe('Dependencies', () => { })}` ); - cy.get('[data-test-subj="latencyChart"]'); - cy.get('[data-test-subj="throughputChart"]'); - cy.get('[data-test-subj="errorRateChart"]'); + cy.getByTestSubj('latencyChart'); + cy.getByTestSubj('throughputChart'); + cy.getByTestSubj('errorRateChart'); cy.contains('opbeans-java').click({ force: true }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts index 19de523c7ab1..d00d8036df3b 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts @@ -68,13 +68,13 @@ describe('Error details', () => { it('shows errors distribution chart', () => { cy.visitKibana(errorDetailsPageHref); cy.contains('Error group 00000'); - cy.get('[data-test-subj="errorDistribution"]').contains('Occurrences'); + cy.getByTestSubj('errorDistribution').contains('Occurrences'); }); it('shows top erroneous transactions table', () => { cy.visitKibana(errorDetailsPageHref); cy.contains('Top 5 affected transactions'); - cy.get('[data-test-subj="topErroneousTransactionsTable"]') + cy.getByTestSubj('topErroneousTransactionsTable') .contains('a', 'GET /apple 🍎') .click(); cy.url().should('include', 'opbeans-java/transactions/view'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts index 301b3384ee2e..8ac95d509d0b 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts @@ -81,14 +81,14 @@ describe('Errors page', () => { it('clicking on type adds a filter in the kuerybar', () => { cy.visitKibana(javaServiceErrorsPageHref); - cy.get('[data-test-subj="headerFilterKuerybar"]') + cy.getByTestSubj('headerFilterKuerybar') .invoke('val') .should('be.empty'); // `force: true` because Cypress says the element is 0x0 cy.contains('exception 0').click({ force: true, }); - cy.get('[data-test-subj="headerFilterKuerybar"]') + cy.getByTestSubj('headerFilterKuerybar') .its('length') .should('be.gt', 0); cy.get('table') @@ -158,7 +158,7 @@ describe('Check detailed statistics API with multiple errors', () => { ]) ); }); - cy.get('[data-test-subj="pagination-button-1"]').click(); + cy.getByTestSubj('pagination-button-1').click(); cy.wait('@errorsDetailedStatistics').then((payload) => { expect(payload.request.body.groupIds).eql( JSON.stringify([ diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts index 2ee2f4f019b1..e0c4a3aedd2b 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts @@ -69,7 +69,7 @@ describe('Home page', () => { cy.contains('Services'); cy.contains('opbeans-rum').click({ force: true }); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'page-load' ); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts index c4e87ac15fbe..4f72e968d81f 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts @@ -44,7 +44,7 @@ describe('Service inventory - header filters', () => { cy.contains('Services'); cy.contains('opbeans-node'); cy.contains('service 1'); - cy.get('[data-test-subj="headerFilterKuerybar"]') + cy.getByTestSubj('headerFilterKuerybar') .type(`service.name: "${specialServiceName}"`) .type('{enter}'); cy.contains('service 1'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts index 015df91d792e..2d40c690a8c9 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts @@ -93,7 +93,7 @@ describe('Service inventory', () => { it('with the correct environment when changing the environment', () => { cy.wait(mainAliasNames); - cy.get('[data-test-subj="environmentFilter"]').type('production'); + cy.getByTestSubj('environmentFilter').type('production'); cy.contains('button', 'production').click(); @@ -175,7 +175,7 @@ describe('Service inventory', () => { ]) ); }); - cy.get('[data-test-subj="pagination-button-1"]').click(); + cy.getByTestSubj('pagination-button-1').click(); cy.wait('@detailedStatisticsRequest').then((payload) => { expect(payload.request.body.serviceNames).eql( JSON.stringify([ 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/e2e/read_only_user/service_overview/errors_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts index b175eb0430ed..d693148010c7 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts @@ -50,16 +50,12 @@ describe('Errors table', () => { it('clicking on type adds a filter in the kuerybar and navigates to errors page', () => { cy.visitKibana(serviceOverviewHref); - cy.get('[data-test-subj="headerFilterKuerybar"]') - .invoke('val') - .should('be.empty'); + cy.getByTestSubj('headerFilterKuerybar').invoke('val').should('be.empty'); // `force: true` because Cypress says the element is 0x0 cy.contains('Exception').click({ force: true, }); - cy.get('[data-test-subj="headerFilterKuerybar"]') - .its('length') - .should('be.gt', 0); + cy.getByTestSubj('headerFilterKuerybar').its('length').should('be.gt', 0); cy.get('table').find('td:contains("Exception")').should('have.length', 1); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts index 6376d544821a..8a2502450669 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts @@ -77,13 +77,13 @@ describe('Service overview - header filters', () => { cy.visitKibana(serviceOverviewHref); cy.contains('opbeans-node'); cy.url().should('not.include', 'transactionType'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); cy.url().should('include', 'transactionType=Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); @@ -94,7 +94,7 @@ describe('Service overview - header filters', () => { cy.intercept('GET', endpoint).as(name); }); cy.visitKibana(serviceOverviewHref); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); @@ -104,9 +104,9 @@ describe('Service overview - header filters', () => { value: 'transactionType=request', }); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); cy.url().should('include', 'transactionType=Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); @@ -129,18 +129,12 @@ describe('Service overview - header filters', () => { }) ); cy.contains('opbeans-java'); - cy.get('[data-test-subj="headerFilterKuerybar"]').type('transaction.n'); + cy.getByTestSubj('headerFilterKuerybar').type('transaction.n'); cy.contains('transaction.name'); - cy.get('[data-test-subj="suggestionContainer"]') - .find('li') - .first() - .click(); - cy.get('[data-test-subj="headerFilterKuerybar"]').type(':'); - cy.get('[data-test-subj="suggestionContainer"]') - .find('li') - .first() - .click(); - cy.get('[data-test-subj="headerFilterKuerybar"]').type('{enter}'); + cy.getByTestSubj('suggestionContainer').find('li').first().click(); + cy.getByTestSubj('headerFilterKuerybar').type(':'); + cy.getByTestSubj('suggestionContainer').find('li').first().click(); + cy.getByTestSubj('headerFilterKuerybar').type('{enter}'); cy.url().should('include', '&kuery=transaction.name'); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts index 03653df2b0bb..578b116a1059 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts @@ -63,7 +63,7 @@ describe('Instances table', () => { it('shows empty message', () => { cy.visitKibana(testServiveHref); cy.contains('test-service'); - cy.get('[data-test-subj="serviceInstancesTableContainer"]').contains( + cy.getByTestSubj('serviceInstancesTableContainer').contains( 'No instances found' ); }); @@ -77,9 +77,7 @@ describe('Instances table', () => { it('hides instances table', () => { cy.visitKibana(serviceRumOverviewHref); cy.contains('opbeans-rum'); - cy.get('[data-test-subj="serviceInstancesTableContainer"]').should( - 'not.exist' - ); + cy.getByTestSubj('serviceInstancesTableContainer').should('not.exist'); }); }); @@ -109,10 +107,8 @@ describe('Instances table', () => { cy.contains(serviceNodeName); cy.wait('@instancesDetailsRequest'); - cy.get( - `[data-test-subj="instanceDetailsButton_${serviceNodeName}"]` - ).realClick(); - cy.get('[data-test-subj="loadingSpinner"]').should('be.visible'); + cy.getByTestSubj(`instanceDetailsButton_${serviceNodeName}`).realClick(); + cy.getByTestSubj('loadingSpinner').should('be.visible'); cy.wait('@instanceDetailsRequest').then(() => { cy.contains('Service'); }); @@ -130,9 +126,7 @@ describe('Instances table', () => { cy.contains(serviceNodeName); cy.wait('@instancesDetailsRequest'); - cy.get( - `[data-test-subj="instanceActionsButton_${serviceNodeName}"]` - ).click(); + cy.getByTestSubj(`instanceActionsButton_${serviceNodeName}`).click(); cy.contains('Pod logs'); cy.contains('Pod metrics'); // cy.contains('Container logs'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts index e8319c8efafe..8173e94557b2 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts @@ -109,13 +109,13 @@ describe('Service Overview', () => { cy.contains('opbeans-node'); // set skipFailures to true to not fail the test when there are accessibility failures checkA11y({ skipFailures: true }); - cy.get('[data-test-subj="latencyChart"]'); - cy.get('[data-test-subj="throughput"]'); - cy.get('[data-test-subj="transactionsGroupTable"]'); - cy.get('[data-test-subj="serviceOverviewErrorsTable"]'); - cy.get('[data-test-subj="dependenciesTable"]'); - cy.get('[data-test-subj="instancesLatencyDistribution"]'); - cy.get('[data-test-subj="serviceOverviewInstancesTable"]'); + cy.getByTestSubj('latencyChart'); + cy.getByTestSubj('throughput'); + cy.getByTestSubj('transactionsGroupTable'); + cy.getByTestSubj('serviceOverviewErrorsTable'); + cy.getByTestSubj('dependenciesTable'); + cy.getByTestSubj('instancesLatencyDistribution'); + cy.getByTestSubj('serviceOverviewInstancesTable'); }); }); @@ -134,17 +134,17 @@ describe('Service Overview', () => { cy.wait('@transactionTypesRequest'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); cy.contains('Transactions').click(); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); @@ -159,18 +159,18 @@ describe('Service Overview', () => { cy.visitKibana(baseUrl); cy.wait('@transactionTypesRequest'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); cy.contains('View transactions').click(); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); @@ -226,7 +226,7 @@ describe('Service Overview', () => { 'suggestionsRequest' ); - cy.get('[data-test-subj="environmentFilter"] input').type('production', { + cy.getByTestSubj('environmentFilter').find('input').type('production', { force: true, }); @@ -235,9 +235,7 @@ describe('Service Overview', () => { value: 'fieldValue=production', }); - cy.get( - '[data-test-subj="comboBoxOptionsList environmentFilter-optionsList"]' - ) + cy.getByTestSubj('comboBoxOptionsList environmentFilter-optionsList') .contains('production') .click({ force: true }); @@ -271,11 +269,11 @@ describe('Service Overview', () => { }); it('when selecting a different comparison window', () => { - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1d'); // selects another comparison type - cy.get('[data-test-subj="comparisonSelect"]').select('1w'); - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w'); + cy.getByTestSubj('comparisonSelect').select('1w'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1w'); cy.expectAPIsToHaveBeenCalledWith({ apisIntercepted: aliasNamesWithComparison, value: 'offset', diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts index 718a2a4a06cf..bce3da42d5a3 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts @@ -101,18 +101,18 @@ describe('Service overview: Time Comparison', () => { cy.visitKibana(serviceOverviewPath); cy.contains('opbeans-java'); // opens the page with "Day before" selected - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1d'); // selects another comparison type - cy.get('[data-test-subj="comparisonSelect"]').select('1w'); - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w'); + cy.getByTestSubj('comparisonSelect').select('1w'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1w'); }); it('changes comparison type when a new time range is selected', () => { cy.visitKibana(serviceOverviewHref); cy.contains('opbeans-java'); // Time comparison default value - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1d'); cy.contains('Day before'); cy.contains('Week before'); @@ -121,17 +121,14 @@ describe('Service overview: Time Comparison', () => { '2021-10-20T00:00:00.000Z' ); - cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click(); + cy.getByTestSubj('superDatePickerApplyTimeButton').click(); - cy.get('[data-test-subj="comparisonSelect"]').should( - 'have.value', - '864000000ms' - ); - cy.get('[data-test-subj="comparisonSelect"]').should( + cy.getByTestSubj('comparisonSelect').should('have.value', '864000000ms'); + cy.getByTestSubj('comparisonSelect').should( 'not.contain.text', 'Day before' ); - cy.get('[data-test-subj="comparisonSelect"]').should( + cy.getByTestSubj('comparisonSelect').should( 'not.contain.text', 'Week before' ); @@ -141,17 +138,14 @@ describe('Service overview: Time Comparison', () => { cy.contains('Week before'); cy.changeTimeRange('Last 24 hours'); - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1d'); cy.contains('Day before'); cy.contains('Week before'); cy.changeTimeRange('Last 7 days'); - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w'); - cy.get('[data-test-subj="comparisonSelect"]').should( - 'contain.text', - 'Week before' - ); - cy.get('[data-test-subj="comparisonSelect"]').should( + cy.getByTestSubj('comparisonSelect').should('have.value', '1w'); + cy.getByTestSubj('comparisonSelect').should('contain.text', 'Week before'); + cy.getByTestSubj('comparisonSelect').should( 'not.contain.text', 'Day before' ); @@ -170,7 +164,7 @@ describe('Service overview: Time Comparison', () => { ); cy.contains('opbeans-java'); cy.wait('@throughputChartRequest'); - cy.get('[data-test-subj="throughput"]') + cy.getByTestSubj('throughput') .get('#echHighlighterClipPath__throughput') .realHover({ position: 'center' }); cy.contains('Week before'); @@ -186,17 +180,17 @@ describe('Service overview: Time Comparison', () => { cy.contains('opbeans-java'); // Comparison is enabled by default - cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled'); + cy.getByTestSubj('comparisonSelect').should('be.enabled'); // toggles off comparison cy.contains('Comparison').click(); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); }); it('calls APIs without comparison time range', () => { cy.visitKibana(serviceOverviewHref); - cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled'); + cy.getByTestSubj('comparisonSelect').should('be.enabled'); const offset = `offset=1d`; // When the page loads it fetches all APIs with comparison time range @@ -212,7 +206,7 @@ describe('Service overview: Time Comparison', () => { // toggles off comparison cy.contains('Comparison').click(); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); // When comparison is disabled APIs are called withou comparison time range cy.wait(apisToIntercept.map(({ name }) => `@${name}`)).then( (interceptions) => { diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts index cddba048e8a1..60b36b10ee4a 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts @@ -50,8 +50,8 @@ describe('Span links', () => { ); cy.contains('Transaction A').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('2 incoming'); @@ -64,8 +64,8 @@ describe('Span links', () => { ); cy.contains('Transaction B').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('1 incoming'); @@ -78,8 +78,8 @@ describe('Span links', () => { ); cy.contains('Transaction C').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerConsumerIds.transactionCId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerConsumerIds.transactionCId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('1 incoming'); @@ -92,8 +92,8 @@ describe('Span links', () => { ); cy.contains('Transaction C').click(); cy.contains('1 Span link'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerConsumerIds.spanCId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerConsumerIds.spanCId}` ).realHover(); cy.contains('1 Span link found'); cy.contains('1 incoming'); @@ -106,8 +106,8 @@ describe('Span links', () => { ); cy.contains('Transaction D').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerMultipleIds.transactionDId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerMultipleIds.transactionDId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('0 incoming'); @@ -120,8 +120,8 @@ describe('Span links', () => { ); cy.contains('Transaction D').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerMultipleIds.spanEId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerMultipleIds.spanEId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('0 incoming'); @@ -136,7 +136,7 @@ describe('Span links', () => { ); cy.contains('Transaction A').click(); cy.contains('Span A').click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('producer-consumer') .should('have.attr', 'href') .and('include', '/services/producer-consumer/overview'); @@ -155,7 +155,7 @@ describe('Span links', () => { 'include', `link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.transactionDId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').should( + cy.getByTestSubj('spanLinkTypeSelect').should( 'contain.text', 'Outgoing links (0)' ); @@ -167,7 +167,7 @@ describe('Span links', () => { ); cy.contains('Transaction B').click(); cy.contains('Span B').click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('consumer-multiple') .should('have.attr', 'href') @@ -178,9 +178,7 @@ describe('Span links', () => { 'include', `link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.spanEId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').select( - 'Outgoing links (1)' - ); + cy.getByTestSubj('spanLinkTypeSelect').select('Outgoing links (1)'); cy.contains('Unknown'); cy.contains('trace#1-span#1'); }); @@ -193,7 +191,7 @@ describe('Span links', () => { cy.get( `[aria-controls="${ids.producerConsumerIds.transactionCId}"]` ).click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('consumer-multiple') .should('have.attr', 'href') @@ -205,9 +203,7 @@ describe('Span links', () => { `link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.spanEId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').select( - 'Outgoing links (1)' - ); + cy.getByTestSubj('spanLinkTypeSelect').select('Outgoing links (1)'); cy.contains('producer-internal-only') .should('have.attr', 'href') .and('include', '/services/producer-internal-only/overview'); @@ -225,7 +221,7 @@ describe('Span links', () => { ); cy.contains('Transaction C').click(); cy.contains('Span C').click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('consumer-multiple') .should('have.attr', 'href') @@ -237,7 +233,7 @@ describe('Span links', () => { `link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.transactionDId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').should( + cy.getByTestSubj('spanLinkTypeSelect').should( 'contain.text', 'Outgoing links (0)' ); @@ -251,7 +247,7 @@ describe('Span links', () => { cy.get( `[aria-controls="${ids.producerMultipleIds.transactionDId}"]` ).click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('producer-consumer') .should('have.attr', 'href') @@ -273,7 +269,7 @@ describe('Span links', () => { `link-to/transaction/${ids.producerInternalOnlyIds.transactionAId}?waterfallItemId=${ids.producerInternalOnlyIds.spanAId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').should( + cy.getByTestSubj('spanLinkTypeSelect').should( 'contain.text', 'Incoming links (0)' ); @@ -285,7 +281,7 @@ describe('Span links', () => { ); cy.contains('Transaction D').click(); cy.contains('Span E').click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('producer-external-only') .should('have.attr', 'href') @@ -307,7 +303,7 @@ describe('Span links', () => { `link-to/transaction/${ids.producerConsumerIds.transactionCId}?waterfallItemId=${ids.producerConsumerIds.transactionCId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').should( + cy.getByTestSubj('spanLinkTypeSelect').should( 'contain.text', 'Incoming links (0)' ); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts index 5172a5f167fc..09bd37f5b0b6 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts @@ -42,15 +42,15 @@ describe('Transaction details', () => { it('shows transaction name and transaction charts', () => { cy.contains('h2', 'GET /api/product'); - cy.get('[data-test-subj="latencyChart"]'); - cy.get('[data-test-subj="throughput"]'); - cy.get('[data-test-subj="transactionBreakdownChart"]'); - cy.get('[data-test-subj="errorRate"]'); + cy.getByTestSubj('latencyChart'); + cy.getByTestSubj('throughput'); + cy.getByTestSubj('transactionBreakdownChart'); + cy.getByTestSubj('errorRate'); }); it('shows top errors table', () => { cy.contains('Top 5 errors'); - cy.get('[data-test-subj="topErrorsForTransactionTable"]') + cy.getByTestSubj('topErrorsForTransactionTable') .contains('a', '[MockError] Foo') .click(); cy.url().should('include', 'opbeans-java/errors'); @@ -58,7 +58,7 @@ describe('Transaction details', () => { describe('when navigating to a trace sample', () => { it('keeps the same trace sample after reloading the page', () => { - cy.get('[data-test-subj="pagination-button-last"]').click(); + cy.getByTestSubj('pagination-button-last').click(); cy.url().then((url) => { cy.reload(); cy.url().should('eq', url); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts index 83753b7fe259..2e7e0d336cd5 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts @@ -49,17 +49,17 @@ describe('Transactions Overview', () => { it('persists transaction type selected when navigating to Overview tab', () => { cy.visitKibana(serviceTransactionsHref); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); cy.get('a[href*="/app/apm/services/opbeans-node/overview"]').click(); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index 7830e791c365..9e6e0189e636 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -52,15 +52,19 @@ Cypress.Commands.add( } ); +Cypress.Commands.add('getByTestSubj', (selector: string) => { + return cy.get(`[data-test-subj="${selector}"]`); +}); + Cypress.Commands.add('changeTimeRange', (value: string) => { - cy.get('[data-test-subj="superDatePickerToggleQuickMenuButton"]').click(); + cy.getByTestSubj('superDatePickerToggleQuickMenuButton').click(); cy.contains(value).click(); }); Cypress.Commands.add('visitKibana', (url: string) => { cy.visit(url); - cy.get('[data-test-subj="kbnLoadingMessage"]').should('exist'); - cy.get('[data-test-subj="kbnLoadingMessage"]').should('not.exist', { + cy.getByTestSubj('kbnLoadingMessage').should('exist'); + cy.getByTestSubj('kbnLoadingMessage').should('not.exist', { timeout: 50000, }); }); @@ -70,13 +74,13 @@ Cypress.Commands.add( (start: string, end: string) => { const format = 'MMM D, YYYY @ HH:mm:ss.SSS'; - cy.get('[data-test-subj="superDatePickerstartDatePopoverButton"]').click(); - cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]') + cy.getByTestSubj('superDatePickerstartDatePopoverButton').click(); + cy.getByTestSubj('superDatePickerAbsoluteDateInput') .eq(0) .clear({ force: true }) .type(moment(start).format(format), { force: true }); - cy.get('[data-test-subj="superDatePickerendDatePopoverButton"]').click(); - cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]') + cy.getByTestSubj('superDatePickerendDatePopoverButton').click(); + cy.getByTestSubj('superDatePickerAbsoluteDateInput') .eq(1) .clear({ force: true }) .type(moment(end).format(format), { force: true }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts index 2235847e584a..5d59d4691820 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts @@ -22,5 +22,6 @@ declare namespace Cypress { value: string; }): void; updateAdvancedSettings(settings: Record): void; + getByTestSubj(selector: string): Chainable>; } } 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/routing/templates/apm_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx index 0a0b17a4e273..c1bda0d2acfe 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx @@ -267,9 +267,9 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { label: i18n.translate('xpack.apm.serviceDetails.metricsTabLabel', { defaultMessage: 'Metrics', }), - append: isServerlessAgent(runtimeName) ? ( + append: isServerlessAgent(runtimeName) && ( - ) : 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/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/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/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/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..1ead4dd9f7d1 --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/delete/translations.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +export { DELETED_CASES } from '../../../common/translations'; + +export const BULK_ACTION_DELETE_LABEL = i18n.translate( + 'xpack.cases.caseTable.bulkActions.deleteCases', + { + defaultMessage: 'Delete cases', + } +); + +export const DELETE_ACTION_LABEL = i18n.translate('xpack.cases.caseTable.action.deleteCase', { + 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/status/translations.ts b/x-pack/plugins/cases/public/components/actions/status/translations.ts new file mode 100644 index 000000000000..68141c7dc344 --- /dev/null +++ b/x-pack/plugins/cases/public/components/actions/status/translations.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +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.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', + }); + +export const BULK_ACTION_STATUS_CLOSE = i18n.translate( + 'xpack.cases.caseTable.bulkActions.status.close', + { + defaultMessage: 'Close selected', + } +); + +export const BULK_ACTION_STATUS_OPEN = i18n.translate( + 'xpack.cases.caseTable.bulkActions.status.open', + { + defaultMessage: 'Open selected', + } +); + +export const BULK_ACTION_STATUS_IN_PROGRESS = i18n.translate( + 'xpack.cases.caseTable.bulkActions.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..30a2389b4f30 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,17 +18,18 @@ import { AppMockRenderer, createAppMockRenderer, noDeleteCasesPermissions, + readCasesPermissions, TestProviders, } from '../../common/mock'; import { useGetCasesMockState, connectorsMock } from '../../containers/mock'; import { StatusAll } from '../../../common/ui/types'; -import { CaseSeverity, CaseStatuses } from '../../../common/api'; +import { CaseStatuses } from '../../../common/api'; 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: [], @@ -236,7 +233,7 @@ describe('AllCasesListGeneric', () => { expect(column.find('span').text()).toEqual(emptyTag); }; - const { result } = renderHook( + const { result } = renderHook( () => useCasesColumns(defaultColumnArgs), { wrapper: ({ children }) => {children}, @@ -244,26 +241,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,27 +268,10 @@ 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', () => { @@ -317,133 +283,6 @@ describe('AllCasesListGeneric', () => { 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')); - }); - - 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('Renders only bulk delete on status all', async () => { - 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() - ); - }); - it('should not render table utility bar when isSelectorView=true', async () => { const wrapper = mount( @@ -522,60 +361,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 +391,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 +410,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 +429,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 +468,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 +699,249 @@ 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('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], + ]; + + 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('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..b85f4ae1826d 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, @@ -88,9 +84,8 @@ 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,8 +123,9 @@ export const CasesTable: FunctionComponent = ({ pagination={pagination} ref={tableRef} rowProps={tableRowProps} - selection={showActions ? selection : undefined} + selection={!isSelectorView ? selection : undefined} sorting={sorting} + hasActions={false} />
); 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..0d8bfab2a7a3 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx @@ -0,0 +1,275 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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(); + }); + }); + + 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('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.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.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..c397047ee98b --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.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 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'; + +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 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}`, + }); + } + + /** + * 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]), + }); + } + + return panelsToBuild; + }, [canDelete, canUpdate, deleteAction, 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..88d596af41fd --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.test.tsx @@ -0,0 +1,334 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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": "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", + }, + ], + } + `); + }); + + 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(); + }); + }); + + 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..bef085ce6d8a --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { Case } from '../../containers/types'; +import { useDeleteAction } from '../actions/delete/use_delete_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 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', + }); + } + + /** + * 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), + }); + } + + return panelsToBuild; + }, [canDelete, canUpdate, deleteAction, isDisabled, selectedCases, 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 51% 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..b996e219c17e 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,32 +295,16 @@ 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) => { @@ -376,52 +318,37 @@ export const useCasesColumns = ({ } 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..415472574f25 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,8 +6,7 @@ */ import React, { FunctionComponent, useCallback, useState } from 'react'; -import { EuiContextMenuPanel } from '@elastic/eui'; -import { CaseStatuses } from '../../../common'; +import { EuiContextMenu } from '@elastic/eui'; import { UtilityBar, UtilityBarAction, @@ -16,142 +15,92 @@ import { UtilityBarText, } from '../utility_bar'; 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 { UtilityBarBulkActions } from '../utility_bar/utility_bar_bulk_actions'; +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 [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]); + /** + * 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; - return ( - - - - - {i18n.SHOWING_CASES(data.total ?? 0)} - - - - {enableBulkActions && ( - <> - - {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} + return ( + <> + + + + + {i18n.SHOWING_CASES(totalCases)} - + + + {!isSelectorView && showBulkActions && ( + <> + + {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} + + + + + + )} - {i18n.BULK_ACTIONS} + {i18n.REFRESH} - - )} - - {i18n.REFRESH} - - - - {isModalVisible ? ( - - ) : null} - - ); -}; + + + + {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 index f082dc4023e7..83c8a16ea029 100644 --- 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 @@ -11,7 +11,6 @@ exports[`UtilityBar it renders 1`] = ` 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/utility_bar.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx index 62988a7a9dd7..52486e32905d 100644 --- 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 @@ -29,9 +29,7 @@ describe('UtilityBar', () => { -

{'Test popover'}

}> - {'Test action'} -
+ {'Test action'}
@@ -57,9 +55,7 @@ describe('UtilityBar', () => { -

{'Test popover'}

}> - {'Test action'} -
+ {'Test action'}
@@ -87,9 +83,7 @@ describe('UtilityBar', () => { -

{'Test popover'}

}> - {'Test action'} -
+ {'Test action'}
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 index 88977fa9bc58..881f4e922bca 100644 --- 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 @@ -5,32 +5,38 @@ * 2.0. */ -import { mount, shallow } from 'enzyme'; import React from 'react'; -import { TestProviders } from '../../common/mock'; +import { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; import { UtilityBarAction } from '.'; describe('UtilityBarAction', () => { + let appMockRenderer: AppMockRenderer; + const dataTestSubj = 'test-bar-action'; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + }); + test('it renders', () => { - const wrapper = shallow( - - {'Test action'} - + const res = appMockRenderer.render( + + {'Test action'} + ); - expect(wrapper.find('UtilityBarAction')).toMatchSnapshot(); + expect(res.getByTestId(dataTestSubj)).toBeInTheDocument(); + expect(res.getByText('Test action')).toBeInTheDocument(); }); test('it renders a popover', () => { - const wrapper = mount( - -

{'Test popover'}

}> - {'Test action'} -
-
+ const res = appMockRenderer.render( + + {'Test action'} + ); - expect(wrapper.find('.euiPopover').first().exists()).toBe(true); + expect(res.getByTestId(`${dataTestSubj}-link-icon`)).toBeInTheDocument(); }); }); 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 index e5bed8702149..b0748f1dd7c9 100644 --- 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 @@ -5,79 +5,19 @@ * 2.0. */ -import { EuiPopover } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; +import React 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} - - ) : ( + ({ dataTestSubj, children, color, disabled, href, iconSide, iconSize, iconType, onClick }) => { + return ( + ( iconSize={iconSize} iconType={iconType} onClick={onClick} + dataTestSubj={dataTestSubj ? `${dataTestSubj}-link-icon` : 'utility-bar-action-link-icon'} > {children} - )} - - ) + + ); + } ); UtilityBarAction.displayName = 'UtilityBarAction'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.test.tsx new file mode 100644 index 000000000000..fa3372cf5233 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock'; +import { UtilityBarBulkActions } from './utility_bar_bulk_actions'; + +describe('UtilityBarBulkActions', () => { + let appMockRenderer: AppMockRenderer; + const closePopover = jest.fn(); + const onButtonClick = jest.fn(); + const dataTestSubj = 'test-bar-action'; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + }); + + it('renders', () => { + const res = appMockRenderer.render( + + + {'Test bulk actions'} + + + ); + + expect(res.getByTestId(dataTestSubj)).toBeInTheDocument(); + expect(res.getByText('button title')).toBeInTheDocument(); + }); + + it('renders a popover', async () => { + const res = appMockRenderer.render( + + + {'Test bulk actions'} + + + ); + + expect(res.getByText('Test bulk actions')).toBeInTheDocument(); + }); + + it('calls onButtonClick', async () => { + const res = appMockRenderer.render( + + + {'Test bulk actions'} + + + ); + + expect(res.getByText('Test bulk actions')).toBeInTheDocument(); + + act(() => { + userEvent.click(res.getByText('button title')); + }); + + expect(onButtonClick).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.tsx new file mode 100644 index 000000000000..afeb93cc221e --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.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 { EuiPopover } from '@elastic/eui'; +import React from 'react'; +import { LinkIcon, LinkIconProps } from '../link_icon'; + +import { BarAction } from './styles'; + +export interface UtilityBarActionProps extends Omit { + isPopoverOpen: boolean; + buttonTitle: string; + closePopover: () => void; + onButtonClick: () => void; + dataTestSubj?: string; +} + +export const UtilityBarBulkActions = React.memo( + ({ + dataTestSubj, + children, + color, + disabled, + href, + iconSide, + iconSize, + iconType, + isPopoverOpen, + onButtonClick, + buttonTitle, + closePopover, + }) => { + return ( + + + {buttonTitle} + + } + > + {children} + + + ); + } +); + +UtilityBarBulkActions.displayName = 'UtilityBarBulkActions'; diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index 2c0ee9bdc2b0..7f90187cd607 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -399,7 +399,7 @@ const basicAction = { export const cases: Case[] = [ basicCase, - { ...pushedCase, id: '1', totalComment: 0, comments: [] }, + { ...pushedCase, id: '1', totalComment: 0, comments: [], status: CaseStatuses['in-progress'] }, { ...pushedCase, updatedAt: laterTime, id: '2', totalComment: 0, comments: [] }, { ...basicCase, id: '3', totalComment: 0, comments: [] }, { ...basicCase, id: '4', totalComment: 0, comments: [] }, @@ -557,7 +557,13 @@ export const pushedCaseSnake = { export const casesSnake: CasesResponse = [ basicCaseSnake, - { ...pushedCaseSnake, id: '1', totalComment: 0, comments: [] }, + { + ...pushedCaseSnake, + id: '1', + totalComment: 0, + comments: [], + status: CaseStatuses['in-progress'], + }, { ...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/ml_inference_pipeline/index.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts index 3bc43fa14d7b..00d893ba9aba 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts @@ -52,6 +52,20 @@ export const generateMlInferencePipelineBody = ({ }, model_id: model.model_id, target_field: `ml.inference.${destinationField}`, + on_failure: [ + { + append: { + field: '_source._ingest.inference_errors', + value: [ + { + pipeline: pipelineName, + message: `Processor 'inference' in pipeline '${pipelineName}' failed with message '{{ _ingest.on_failure_message }}'`, + timestamp: '{{{ _ingest.timestamp }}}', + }, + ], + }, + }, + ], }, }, { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx index eeed5bb377fe..8c7522dc868b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx @@ -21,7 +21,9 @@ import { EuiFlexItem, } from '@elastic/eui'; -import { USERNAME_LABEL, PASSWORD_LABEL, TOKEN_LABEL } from '../../../../shared/constants'; +import { i18n } from '@kbn/i18n'; + +import { USERNAME_LABEL, PASSWORD_LABEL } from '../../../../shared/constants'; import { AuthenticationPanelLogic } from './authentication_panel_logic'; import { AUTHENTICATION_LABELS } from './constants'; @@ -82,7 +84,14 @@ export const AuthenticationPanelEditContent: React.FC = () => { onChange={() => selectAuthOption('raw')} > - + { setScheduling({ ...scheduling, interval: expression }); setHasChanges(true); }} + frequencyBlockList={['MINUTE']} />
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx index fba38e958163..3e3582bb619f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx @@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { generateEncodedPath } from '../../../../../shared/encode_path_params'; -import { EuiLinkTo, EuiButtonTo } from '../../../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../../../shared/react_router_helpers'; import { SEARCH_INDEX_TAB_PATH } from '../../../../routes'; import { IndexNameLogic } from '../../index_name_logic'; @@ -31,19 +31,7 @@ export const NativeConnectorAdvancedConfiguration: React.FC = () => { - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.recurringScheduleLinkLabel', - { - defaultMessage: 'recurring sync schedule', - } - )} - - ), - }} + defaultMessage="Finalize your connector by triggering a one time sync, or setting a recurring sync schedule." /> 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 }, @@ -223,10 +220,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..18190cef67ca 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'; @@ -21,19 +29,29 @@ 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: 'json-configurations', + name: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations', + { + defaultMessage: 'JSON configurations', + } + ), + }, + ]; + return ( <> @@ -82,8 +100,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 +151,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..165cdfc99eb8 --- /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,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { 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'], + ], + }, + 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); + } + }, + }), + 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/enterprise_search_overview/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx index 01997959ec41..7be1264f2a9d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx @@ -71,7 +71,7 @@ export const ProductSelector: React.FC = ({ <> = ({ title, butto

{i18n.translate('xpack.enterpriseSearch.emptyState.description', { defaultMessage: - 'An Elasticsearch index is where your content gets stored. Get started by creating an Elasticsearch index and selecting an ingestion method. Options include the Elastic web crawler, third party data integrations, or using Elasticsearch API endpoints.', + 'Your content is stored in an Elasticsearch index. Get started by creating an Elasticsearch index and selecting an ingestion method. Options include the Elastic web crawler, third party data integrations, or using Elasticsearch API endpoints.', })}

diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts index fa982547f7fc..edfef1ba1f3f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts @@ -15,10 +15,6 @@ export const PASSWORD_LABEL = i18n.translate('xpack.enterpriseSearch.passwordLab defaultMessage: 'Password', }); -export const TOKEN_LABEL = i18n.translate('xpack.enterpriseSearch.tokenLabel', { - defaultMessage: 'Token', -}); - export const TYPE_LABEL = i18n.translate('xpack.enterpriseSearch.typeLabel', { defaultMessage: 'Type', }); 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/pipelines/create_pipeline_definitions.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts index 5abd7db73170..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 @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { merge } from 'lodash'; import { ElasticsearchClient } from '@kbn/core/server'; @@ -29,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); }); }); @@ -41,7 +42,7 @@ describe('createIndexPipelineDefinitions util function', () => { describe('formatMlPipelineBody util function', () => { const pipelineName = 'ml-inference-my-ml-proc'; const modelId = 'my-model-id'; - let modelInputField = 'my-model-input-field'; + const modelInputField = 'my-model-input-field'; const modelType = 'pytorch'; const inferenceConfigKey = 'my-model-type'; const modelTypes = ['pytorch', 'my-model-type']; @@ -49,6 +50,55 @@ describe('formatMlPipelineBody util function', () => { const sourceField = 'my-source-field'; const destField = 'my-dest-field'; + const expectedResult = { + description: '', + processors: [ + { + remove: { + field: `ml.inference.${destField}`, + ignore_missing: true, + }, + }, + { + inference: { + field_map: { + [sourceField]: modelInputField, + }, + model_id: modelId, + target_field: `ml.inference.${destField}`, + on_failure: [ + { + append: { + field: '_source._ingest.inference_errors', + value: [ + { + pipeline: pipelineName, + message: `Processor 'inference' in pipeline '${pipelineName}' failed with message '{{ _ingest.on_failure_message }}'`, + timestamp: '{{{ _ingest.timestamp }}}', + }, + ], + }, + }, + ], + }, + }, + { + append: { + field: '_source._ingest.processors', + value: [ + { + model_version: modelVersion, + pipeline: pipelineName, + processed_timestamp: '{{{ _ingest.timestamp }}}', + types: modelTypes, + }, + ], + }, + }, + ], + version: 1, + }; + const mockClient = { ml: { getTrainedModels: jest.fn(), @@ -60,41 +110,6 @@ describe('formatMlPipelineBody util function', () => { }); it('should return the pipeline body', async () => { - const expectedResult = { - description: '', - processors: [ - { - remove: { - field: `ml.inference.${destField}`, - ignore_missing: true, - }, - }, - { - inference: { - field_map: { - [sourceField]: modelInputField, - }, - model_id: modelId, - target_field: `ml.inference.${destField}`, - }, - }, - { - append: { - field: '_source._ingest.processors', - value: [ - { - model_version: modelVersion, - pipeline: pipelineName, - processed_timestamp: '{{{ _ingest.timestamp }}}', - types: modelTypes, - }, - ], - }, - }, - ], - version: 1, - }; - const mockResponse = { count: 1, trained_model_configs: [ @@ -136,41 +151,19 @@ describe('formatMlPipelineBody util function', () => { }); it('should insert a placeholder if model has no input fields', async () => { - modelInputField = 'MODEL_INPUT_FIELD'; - const expectedResult = { - description: '', + const expectedResultWithNoInputField = merge({}, expectedResult, { processors: [ - { - remove: { - field: `ml.inference.${destField}`, - ignore_missing: true, - }, - }, + {}, // append - we'll leave it untouched { inference: { field_map: { - [sourceField]: modelInputField, + [sourceField]: 'MODEL_INPUT_FIELD', }, - model_id: modelId, - target_field: `ml.inference.${destField}`, - }, - }, - { - append: { - field: '_source._ingest.processors', - value: [ - { - model_version: modelVersion, - pipeline: pipelineName, - processed_timestamp: '{{{ _ingest.timestamp }}}', - types: modelTypes, - }, - ], }, }, ], - version: 1, - }; + }); + const mockResponse = { count: 1, trained_model_configs: [ @@ -193,7 +186,7 @@ describe('formatMlPipelineBody util function', () => { destField, mockClient as unknown as ElasticsearchClient ); - expect(actualResult).toEqual(expectedResult); + expect(actualResult).toEqual(expectedResultWithNoInputField); expect(mockClient.ml.getTrainedModels).toHaveBeenCalledTimes(1); }); }); 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.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index db46da11f5f5..ef6f8131ee2c 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 @@ -13,6 +13,7 @@ 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'; @@ -28,6 +29,7 @@ import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_i import { generateApiKey } from '../../lib/indices/generate_api_key'; 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 +295,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' }, }); }) 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/openapi/components/schemas/settings.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/settings.yaml index 952683400b23..280460771989 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/settings.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/settings.yaml @@ -5,8 +5,6 @@ properties: type: string has_seen_add_data_notice: type: boolean - has_seen_fleet_migration_notice: - type: boolean fleet_server_hosts: type: array items: diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index fa54f8c943e2..ea2ad78dc10c 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -94,7 +94,12 @@ interface AgentBase { export interface Agent extends AgentBase { id: string; 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']; + }>; status?: AgentStatus; packages: string[]; sort?: Array; diff --git a/x-pack/plugins/fleet/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts index 269fad8391b9..17bbe7a73c1d 100644 --- a/x-pack/plugins/fleet/common/types/models/settings.ts +++ b/x-pack/plugins/fleet/common/types/models/settings.ts @@ -9,7 +9,6 @@ import type { SavedObjectAttributes } from '@kbn/core/public'; export interface BaseSettings { has_seen_add_data_notice?: boolean; - has_seen_fleet_migration_notice?: boolean; fleet_server_hosts: string[]; } 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/single_page_layout/hooks/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx new file mode 100644 index 000000000000..55e91154060b --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { ExperimentalFeaturesService } from '../../../../../services'; +import { + generateCreatePackagePolicyDevToolsRequest, + generateCreateAgentPolicyDevToolsRequest, +} from '../../../services'; +import { + FLEET_SYSTEM_PACKAGE, + HIDDEN_API_REFERENCE_PACKAGES, +} from '../../../../../../../../common/constants'; +import type { PackageInfo, NewAgentPolicy, NewPackagePolicy } from '../../../../../types'; +import { SelectedPolicyTab } from '../../components'; + +export function useDevToolsRequest({ + newAgentPolicy, + packagePolicy, + packageInfo, + selectedPolicyTab, + withSysMonitoring, +}: { + withSysMonitoring: boolean; + selectedPolicyTab: SelectedPolicyTab; + newAgentPolicy: NewAgentPolicy; + packagePolicy: NewPackagePolicy; + packageInfo?: PackageInfo; +}) { + const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } = + ExperimentalFeaturesService.get(); + + const showDevtoolsRequest = + !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') && + isShowDevtoolRequestExperimentEnabled; + + const [devtoolRequest, devtoolRequestDescription] = useMemo(() => { + if (selectedPolicyTab === SelectedPolicyTab.NEW) { + const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE; + return [ + `${generateCreateAgentPolicyDevToolsRequest( + newAgentPolicy, + withSysMonitoring && !packagePolicyIsSystem + )}\n\n${generateCreatePackagePolicyDevToolsRequest({ + ...packagePolicy, + })}`, + i18n.translate( + 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription', + { + defaultMessage: + 'These Kibana requests creates a new agent policy and a new package policy.', + } + ), + ]; + } + + return [ + generateCreatePackagePolicyDevToolsRequest({ + ...packagePolicy, + }), + i18n.translate('xpack.fleet.createPackagePolicy.devtoolsRequestDescription', { + defaultMessage: 'This Kibana request creates a new package policy.', + }), + ]; + }, [packagePolicy, newAgentPolicy, withSysMonitoring, selectedPolicyTab]); + + return { showDevtoolsRequest, devtoolRequest, devtoolRequestDescription }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx new file mode 100644 index 000000000000..e0f206ef612a --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.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 { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { safeLoad } from 'js-yaml'; + +import type { + AgentPolicy, + NewPackagePolicy, + NewAgentPolicy, + CreatePackagePolicyRequest, + PackagePolicy, + PackageInfo, +} from '../../../../../types'; +import { + useStartServices, + sendCreateAgentPolicy, + sendCreatePackagePolicy, + sendBulkInstallPackages, +} from '../../../../../hooks'; +import { isVerificationError } from '../../../../../services'; +import { FLEET_ELASTIC_AGENT_PACKAGE, FLEET_SYSTEM_PACKAGE } from '../../../../../../../../common'; +import { useConfirmForceInstall } from '../../../../../../integrations/hooks'; +import { validatePackagePolicy, validationHasErrors } from '../../services'; +import type { PackagePolicyValidationResults } from '../../services'; +import type { PackagePolicyFormState } from '../../types'; +import { SelectedPolicyTab } from '../../components'; +import { useOnSaveNavigate } from '../../hooks'; + +async function createAgentPolicy({ + packagePolicy, + newAgentPolicy, + withSysMonitoring, +}: { + packagePolicy: NewPackagePolicy; + newAgentPolicy: NewAgentPolicy; + withSysMonitoring: boolean; +}): Promise { + // do not create agent policy with system integration if package policy already is for system package + const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE; + const resp = await sendCreateAgentPolicy(newAgentPolicy, { + withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem, + }); + if (resp.error) { + throw resp.error; + } + if (!resp.data) { + throw new Error('Invalid agent policy creation no data'); + } + return resp.data.item; +} + +async function savePackagePolicy(pkgPolicy: CreatePackagePolicyRequest['body']) { + const result = await sendCreatePackagePolicy(pkgPolicy); + + return result; +} + +export function useOnSubmit({ + agentCount, + selectedPolicyTab, + newAgentPolicy, + withSysMonitoring, + queryParamsPolicyId, + packageInfo, +}: { + packageInfo?: PackageInfo; + newAgentPolicy: NewAgentPolicy; + withSysMonitoring: boolean; + selectedPolicyTab: SelectedPolicyTab; + agentCount: number; + queryParamsPolicyId: string | undefined; +}) { + const { notifications } = useStartServices(); + const confirmForceInstall = useConfirmForceInstall(); + // only used to store the resulting package policy once saved + const [savedPackagePolicy, setSavedPackagePolicy] = useState(); + // Form state + const [formState, setFormState] = useState('VALID'); + + const [agentPolicy, setAgentPolicy] = useState(); + // New package policy state + const [packagePolicy, setPackagePolicy] = useState({ + name: '', + description: '', + namespace: 'default', + policy_id: '', + enabled: true, + inputs: [], + }); + + // Validation state + const [validationResults, setValidationResults] = useState(); + const [hasAgentPolicyError, setHasAgentPolicyError] = useState(false); + const hasErrors = validationResults ? validationHasErrors(validationResults) : false; + + // Update agent policy method + const updateAgentPolicy = useCallback( + (updatedAgentPolicy: AgentPolicy | undefined) => { + if (updatedAgentPolicy) { + setAgentPolicy(updatedAgentPolicy); + if (packageInfo) { + setHasAgentPolicyError(false); + } + } else { + setHasAgentPolicyError(true); + setAgentPolicy(undefined); + } + + // eslint-disable-next-line no-console + console.debug('Agent policy updated', updatedAgentPolicy); + }, + [packageInfo, setAgentPolicy] + ); + // Update package policy validation + const updatePackagePolicyValidation = useCallback( + (newPackagePolicy?: NewPackagePolicy) => { + if (packageInfo) { + const newValidationResult = validatePackagePolicy( + newPackagePolicy || packagePolicy, + packageInfo, + safeLoad + ); + setValidationResults(newValidationResult); + // eslint-disable-next-line no-console + console.debug('Package policy validation results', newValidationResult); + + return newValidationResult; + } + }, + [packagePolicy, packageInfo] + ); + // Update package policy method + const updatePackagePolicy = useCallback( + (updatedFields: Partial) => { + const newPackagePolicy = { + ...packagePolicy, + ...updatedFields, + }; + setPackagePolicy(newPackagePolicy); + + // eslint-disable-next-line no-console + console.debug('Package policy updated', newPackagePolicy); + const newValidationResults = updatePackagePolicyValidation(newPackagePolicy); + const hasPackage = newPackagePolicy.package; + const hasValidationErrors = newValidationResults + ? validationHasErrors(newValidationResults) + : false; + const hasAgentPolicy = newPackagePolicy.policy_id && newPackagePolicy.policy_id !== ''; + if ( + hasPackage && + (hasAgentPolicy || selectedPolicyTab === SelectedPolicyTab.NEW) && + !hasValidationErrors + ) { + setFormState('VALID'); + } else { + setFormState('INVALID'); + } + }, + [packagePolicy, setFormState, updatePackagePolicyValidation, selectedPolicyTab] + ); + + const onSaveNavigate = useOnSaveNavigate({ + packagePolicy, + queryParamsPolicyId, + }); + + const navigateAddAgent = (policy?: PackagePolicy) => + onSaveNavigate(policy, ['openEnrollmentFlyout']); + + const navigateAddAgentHelp = (policy?: PackagePolicy) => + onSaveNavigate(policy, ['showAddAgentHelp']); + + const onSubmit = useCallback( + async ({ + force, + overrideCreatedAgentPolicy, + }: { overrideCreatedAgentPolicy?: AgentPolicy; force?: boolean } = {}) => { + if (formState === 'VALID' && hasErrors) { + setFormState('INVALID'); + return; + } + if (agentCount !== 0 && formState !== 'CONFIRM') { + setFormState('CONFIRM'); + return; + } + let createdPolicy = overrideCreatedAgentPolicy; + if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) { + try { + setFormState('LOADING'); + if ((withSysMonitoring || newAgentPolicy.monitoring_enabled?.length) ?? 0 > 0) { + const packagesToPreinstall: string[] = []; + if (packageInfo) { + packagesToPreinstall.push(packageInfo.name); + } + if (withSysMonitoring) { + packagesToPreinstall.push(FLEET_SYSTEM_PACKAGE); + } + if (newAgentPolicy.monitoring_enabled?.length ?? 0 > 0) { + packagesToPreinstall.push(FLEET_ELASTIC_AGENT_PACKAGE); + } + + if (packagesToPreinstall.length > 0) { + await sendBulkInstallPackages([...new Set(packagesToPreinstall)]); + } + } + + createdPolicy = await createAgentPolicy({ + newAgentPolicy, + packagePolicy, + withSysMonitoring, + }); + setAgentPolicy(createdPolicy); + updatePackagePolicy({ policy_id: createdPolicy.id }); + } catch (e) { + setFormState('VALID'); + notifications.toasts.addError(e, { + title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', { + defaultMessage: 'Unable to create agent policy', + }), + }); + return; + } + } + + setFormState('LOADING'); + // passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately + const { error, data } = await savePackagePolicy({ + ...packagePolicy, + policy_id: createdPolicy?.id ?? packagePolicy.policy_id, + force, + }); + setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS'); + if (!error) { + setSavedPackagePolicy(data!.item); + + const hasAgentsAssigned = agentCount && agentPolicy; + if (!hasAgentsAssigned) { + setFormState('SUBMITTED_NO_AGENTS'); + return; + } + onSaveNavigate(data!.item); + + notifications.toasts.addSuccess({ + title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', { + defaultMessage: `'{packagePolicyName}' integration added.`, + values: { + packagePolicyName: packagePolicy.name, + }, + }), + text: hasAgentsAssigned + ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', { + defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`, + values: { + agentPolicyName: agentPolicy!.name, + }, + }) + : undefined, + 'data-test-subj': 'packagePolicyCreateSuccessToast', + }); + } else { + if (isVerificationError(error)) { + setFormState('VALID'); // don't show the add agent modal + const forceInstall = await confirmForceInstall(packagePolicy.package!); + + if (forceInstall) { + // skip creating the agent policy because it will have already been successfully created + onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true }); + } + return; + } + notifications.toasts.addError(error, { + title: 'Error', + }); + setFormState('VALID'); + } + }, + [ + formState, + hasErrors, + agentCount, + selectedPolicyTab, + packagePolicy, + notifications.toasts, + agentPolicy, + onSaveNavigate, + confirmForceInstall, + newAgentPolicy, + updatePackagePolicy, + withSysMonitoring, + packageInfo, + ] + ); + + return { + agentPolicy, + updateAgentPolicy, + packagePolicy, + updatePackagePolicy, + savedPackagePolicy, + onSubmit, + formState, + setFormState, + hasErrors, + validationResults, + setValidationResults, + hasAgentPolicyError, + setHasAgentPolicyError, + // TODO check + navigateAddAgent, + navigateAddAgentHelp, + }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx new file mode 100644 index 000000000000..33d1cee84159 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx @@ -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 { useDevToolsRequest } from './devtools_request'; +export { useOnSubmit } from './form'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index fae3c84f2126..02f36e2cadcf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -21,35 +21,16 @@ import { EuiErrorBoundary, } from '@elastic/eui'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; -import { safeLoad } from 'js-yaml'; -import { useCancelAddPackagePolicy, useOnSaveNavigate } from '../hooks'; -import type { CreatePackagePolicyRequest } from '../../../../../../../common/types'; +import { useCancelAddPackagePolicy } from '../hooks'; import { splitPkgKey } from '../../../../../../../common/services'; -import { - dataTypes, - FLEET_SYSTEM_PACKAGE, - HIDDEN_API_REFERENCE_PACKAGES, -} from '../../../../../../../common/constants'; -import { useConfirmForceInstall } from '../../../../../integrations/hooks'; -import type { - AgentPolicy, - NewAgentPolicy, - NewPackagePolicy, - PackagePolicy, -} from '../../../../types'; -import { - sendCreatePackagePolicy, - useStartServices, - useConfig, - sendGetAgentStatus, - useGetPackageInfoByKey, - sendCreateAgentPolicy, -} from '../../../../hooks'; +import { dataTypes } from '../../../../../../../common/constants'; +import type { NewAgentPolicy } from '../../../../types'; +import { useConfig, sendGetAgentStatus, useGetPackageInfoByKey } from '../../../../hooks'; import { Loading, - Error, + Error as ErrorComponent, ExtensionWrapper, DevtoolsRequestFlyoutButton, } from '../../../../components'; @@ -57,34 +38,21 @@ import { import { agentPolicyFormValidation, ConfirmDeployAgentPolicyModal } from '../../components'; import { useUIExtension } from '../../../../hooks'; import type { PackagePolicyEditExtensionComponentProps } from '../../../../types'; -import { - pkgKeyFromPackageInfo, - isVerificationError, - ExperimentalFeaturesService, -} from '../../../../services'; +import { pkgKeyFromPackageInfo } from '../../../../services'; -import type { - PackagePolicyFormState, - AddToPolicyParams, - CreatePackagePolicyParams, -} from '../types'; +import type { AddToPolicyParams, CreatePackagePolicyParams } from '../types'; import { IntegrationBreadcrumb } from '../components'; -import type { PackagePolicyValidationResults } from '../services'; -import { validatePackagePolicy, validationHasErrors } from '../services'; import { StepConfigurePackagePolicy, StepDefinePackagePolicy, SelectedPolicyTab, StepSelectHosts, } from '../components'; -import { - generateCreatePackagePolicyDevToolsRequest, - generateCreateAgentPolicyDevToolsRequest, -} from '../../services'; import { CreatePackagePolicySinglePageLayout, PostInstallAddAgentModal } from './components'; +import { useDevToolsRequest, useOnSubmit } from './hooks'; const StepsWithLessPadding = styled(EuiSteps)` .euiStep__content { @@ -106,12 +74,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ from, queryParamsPolicyId, }) => { - const { notifications } = useStartServices(); const { agents: { enabled: isFleetEnabled }, } = useConfig(); const { params } = useRouteMatch(); - const [agentPolicy, setAgentPolicy] = useState(); const [newAgentPolicy, setNewAgentPolicy] = useState({ name: 'Agent policy 1', @@ -123,64 +89,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ const [withSysMonitoring, setWithSysMonitoring] = useState(true); const validation = agentPolicyFormValidation(newAgentPolicy); - // only used to store the resulting package policy once saved - const [savedPackagePolicy, setSavedPackagePolicy] = useState(); - - // Retrieve agent count - const agentPolicyId = agentPolicy?.id; - - const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({ - from, - pkgkey: params.pkgkey, - agentPolicyId, - }); - useEffect(() => { - const getAgentCount = async () => { - const { data } = await sendGetAgentStatus({ policyId: agentPolicyId }); - if (data?.results.total !== undefined) { - setAgentCount(data.results.total); - } - }; - - if (isFleetEnabled && agentPolicyId) { - getAgentCount(); - } - }, [agentPolicyId, isFleetEnabled]); - const [agentCount, setAgentCount] = useState(0); - const [selectedPolicyTab, setSelectedPolicyTab] = useState( queryParamsPolicyId ? SelectedPolicyTab.EXISTING : SelectedPolicyTab.NEW ); - // New package policy state - const [packagePolicy, setPackagePolicy] = useState({ - name: '', - description: '', - namespace: 'default', - policy_id: '', - enabled: true, - inputs: [], - }); - - const onSaveNavigate = useOnSaveNavigate({ - packagePolicy, - queryParamsPolicyId, - }); - const navigateAddAgent = (policy?: PackagePolicy) => - onSaveNavigate(policy, ['openEnrollmentFlyout']); - - const navigateAddAgentHelp = (policy?: PackagePolicy) => - onSaveNavigate(policy, ['showAddAgentHelp']); - - const confirmForceInstall = useConfirmForceInstall(); - - // Validation state - const [validationResults, setValidationResults] = useState(); - const [hasAgentPolicyError, setHasAgentPolicyError] = useState(false); - - // Form state - const [formState, setFormState] = useState('VALID'); - const { pkgName, pkgVersion } = splitPkgKey(params.pkgkey); // Fetch package info const { @@ -194,43 +106,50 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ } }, [packageInfoData]); - // Update agent policy method - const updateAgentPolicy = useCallback( - (updatedAgentPolicy: AgentPolicy | undefined) => { - if (updatedAgentPolicy) { - setAgentPolicy(updatedAgentPolicy); - if (packageInfo) { + const [agentCount, setAgentCount] = useState(0); + + // Save package policy + const { + onSubmit, + updatePackagePolicy, + packagePolicy, + agentPolicy, + updateAgentPolicy, + savedPackagePolicy, + formState, + setFormState, + navigateAddAgent, + navigateAddAgentHelp, + setHasAgentPolicyError, + validationResults, + hasAgentPolicyError, + } = useOnSubmit({ + agentCount, + packageInfo, + newAgentPolicy, + selectedPolicyTab, + withSysMonitoring, + queryParamsPolicyId, + }); + + const setPolicyValidation = useCallback( + (selectedTab: SelectedPolicyTab, updatedAgentPolicy: NewAgentPolicy) => { + if (selectedTab === SelectedPolicyTab.NEW) { + if ( + !updatedAgentPolicy.name || + updatedAgentPolicy.name.trim() === '' || + !updatedAgentPolicy.namespace || + updatedAgentPolicy.namespace.trim() === '' + ) { + setHasAgentPolicyError(true); + } else { setHasAgentPolicyError(false); } - } else { - setHasAgentPolicyError(true); - setAgentPolicy(undefined); } - - // eslint-disable-next-line no-console - console.debug('Agent policy updated', updatedAgentPolicy); }, - [packageInfo, setAgentPolicy] + [setHasAgentPolicyError] ); - const setPolicyValidation = ( - selectedTab: SelectedPolicyTab, - updatedAgentPolicy: NewAgentPolicy - ) => { - if (selectedTab === SelectedPolicyTab.NEW) { - if ( - !updatedAgentPolicy.name || - updatedAgentPolicy.name.trim() === '' || - !updatedAgentPolicy.namespace || - updatedAgentPolicy.namespace.trim() === '' - ) { - setHasAgentPolicyError(true); - } else { - setHasAgentPolicyError(false); - } - } - }; - const updateNewAgentPolicy = useCallback( (updatedFields: Partial) => { const updatedAgentPolicy = { @@ -240,7 +159,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ setNewAgentPolicy(updatedAgentPolicy); setPolicyValidation(selectedPolicyTab, updatedAgentPolicy); }, - [setNewAgentPolicy, newAgentPolicy, selectedPolicyTab] + [setNewAgentPolicy, setPolicyValidation, newAgentPolicy, selectedPolicyTab] ); const updateSelectedPolicyTab = useCallback( @@ -248,58 +167,29 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ setSelectedPolicyTab(selectedTab); setPolicyValidation(selectedTab, newAgentPolicy); }, - [setSelectedPolicyTab, newAgentPolicy] + [setSelectedPolicyTab, setPolicyValidation, newAgentPolicy] ); - const hasErrors = validationResults ? validationHasErrors(validationResults) : false; - // Update package policy validation - const updatePackagePolicyValidation = useCallback( - (newPackagePolicy?: NewPackagePolicy) => { - if (packageInfo) { - const newValidationResult = validatePackagePolicy( - newPackagePolicy || packagePolicy, - packageInfo, - safeLoad - ); - setValidationResults(newValidationResult); - // eslint-disable-next-line no-console - console.debug('Package policy validation results', newValidationResult); - - return newValidationResult; - } - }, - [packagePolicy, packageInfo] - ); + // Retrieve agent count + const agentPolicyId = agentPolicy?.id; - // Update package policy method - const updatePackagePolicy = useCallback( - (updatedFields: Partial) => { - const newPackagePolicy = { - ...packagePolicy, - ...updatedFields, - }; - setPackagePolicy(newPackagePolicy); - - // eslint-disable-next-line no-console - console.debug('Package policy updated', newPackagePolicy); - const newValidationResults = updatePackagePolicyValidation(newPackagePolicy); - const hasPackage = newPackagePolicy.package; - const hasValidationErrors = newValidationResults - ? validationHasErrors(newValidationResults) - : false; - const hasAgentPolicy = newPackagePolicy.policy_id && newPackagePolicy.policy_id !== ''; - if ( - hasPackage && - (hasAgentPolicy || selectedPolicyTab === SelectedPolicyTab.NEW) && - !hasValidationErrors - ) { - setFormState('VALID'); - } else { - setFormState('INVALID'); + const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({ + from, + pkgkey: params.pkgkey, + agentPolicyId, + }); + useEffect(() => { + const getAgentCount = async () => { + const { data } = await sendGetAgentStatus({ policyId: agentPolicyId }); + if (data?.results.total !== undefined) { + setAgentCount(data.results.total); } - }, - [packagePolicy, updatePackagePolicyValidation, selectedPolicyTab] - ); + }; + + if (isFleetEnabled && agentPolicyId) { + getAgentCount(); + } + }, [agentPolicyId, isFleetEnabled]); const handleExtensionViewOnChange = useCallback< PackagePolicyEditExtensionComponentProps['onChange'] @@ -313,132 +203,16 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ return prevState; }); }, - [updatePackagePolicy] - ); - - // Save package policy - const savePackagePolicy = useCallback( - async (pkgPolicy: CreatePackagePolicyRequest['body']) => { - setFormState('LOADING'); - const result = await sendCreatePackagePolicy(pkgPolicy); - setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS'); - return result; - }, - [agentCount] + [updatePackagePolicy, setFormState] ); - const createAgentPolicy = useCallback(async (): Promise => { - let createdAgentPolicy; - setFormState('LOADING'); - // do not create agent policy with system integration if package policy already is for system package - const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE; - const resp = await sendCreateAgentPolicy(newAgentPolicy, { - withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem, - }); - if (resp.error) { - setFormState('VALID'); - throw resp.error; - } - if (resp.data) { - createdAgentPolicy = resp.data.item; - setAgentPolicy(createdAgentPolicy); - updatePackagePolicy({ policy_id: createdAgentPolicy.id }); - } - return createdAgentPolicy; - }, [packagePolicy?.package?.name, newAgentPolicy, withSysMonitoring, updatePackagePolicy]); - - const onSubmit = useCallback( - async ({ - force, - overrideCreatedAgentPolicy, - }: { overrideCreatedAgentPolicy?: AgentPolicy; force?: boolean } = {}) => { - if (formState === 'VALID' && hasErrors) { - setFormState('INVALID'); - return; - } - if (agentCount !== 0 && formState !== 'CONFIRM') { - setFormState('CONFIRM'); - return; - } - let createdPolicy = overrideCreatedAgentPolicy; - if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) { - try { - createdPolicy = await createAgentPolicy(); - } catch (e) { - notifications.toasts.addError(e, { - title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', { - defaultMessage: 'Unable to create agent policy', - }), - }); - return; - } - } - - setFormState('LOADING'); - // passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately - const { error, data } = await savePackagePolicy({ - ...packagePolicy, - policy_id: createdPolicy?.id ?? packagePolicy.policy_id, - force, - }); - if (!error) { - setSavedPackagePolicy(data!.item); - - const hasAgentsAssigned = agentCount && agentPolicy; - if (!hasAgentsAssigned) { - setFormState('SUBMITTED_NO_AGENTS'); - return; - } - onSaveNavigate(data!.item); - - notifications.toasts.addSuccess({ - title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', { - defaultMessage: `'{packagePolicyName}' integration added.`, - values: { - packagePolicyName: packagePolicy.name, - }, - }), - text: hasAgentsAssigned - ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', { - defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`, - values: { - agentPolicyName: agentPolicy!.name, - }, - }) - : undefined, - 'data-test-subj': 'packagePolicyCreateSuccessToast', - }); - } else { - if (isVerificationError(error)) { - setFormState('VALID'); // don't show the add agent modal - const forceInstall = await confirmForceInstall(packagePolicy.package!); - - if (forceInstall) { - // skip creating the agent policy because it will have already been successfully created - onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true }); - } - return; - } - notifications.toasts.addError(error, { - title: 'Error', - }); - setFormState('VALID'); - } - }, - [ - formState, - hasErrors, - agentCount, - selectedPolicyTab, - savePackagePolicy, - packagePolicy, - createAgentPolicy, - notifications.toasts, - agentPolicy, - onSaveNavigate, - confirmForceInstall, - ] - ); + const { devtoolRequest, devtoolRequestDescription, showDevtoolsRequest } = useDevToolsRequest({ + newAgentPolicy, + packagePolicy, + selectedPolicyTab, + withSysMonitoring, + packageInfo, + }); const integrationInfo = useMemo( () => @@ -488,6 +262,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ withSysMonitoring, updateSelectedPolicyTab, queryParamsPolicyId, + setHasAgentPolicyError, ] ); @@ -564,47 +339,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ }, ]; - const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } = - ExperimentalFeaturesService.get(); - - const showDevtoolsRequest = - !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') && - isShowDevtoolRequestExperimentEnabled; - - const [devtoolRequest, devtoolRequestDescription] = useMemo(() => { - if (selectedPolicyTab === SelectedPolicyTab.NEW) { - const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE; - return [ - `${generateCreateAgentPolicyDevToolsRequest( - newAgentPolicy, - withSysMonitoring && !packagePolicyIsSystem - )}\n\n${generateCreatePackagePolicyDevToolsRequest({ - ...packagePolicy, - })}`, - i18n.translate( - 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription', - { - defaultMessage: - 'These Kibana requests creates a new agent policy and a new package policy.', - } - ), - ]; - } - - return [ - generateCreatePackagePolicyDevToolsRequest({ - ...packagePolicy, - }), - i18n.translate('xpack.fleet.createPackagePolicy.devtoolsRequestDescription', { - defaultMessage: 'This Kibana request creates a new package policy.', - }), - ]; - }, [packagePolicy, newAgentPolicy, withSysMonitoring, selectedPolicyTab]); - // Display package error if there is one if (packageInfoError) { return ( - { }); 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/fleet/sections/agents/components/fleet_server_upgrade_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_upgrade_modal.tsx deleted file mode 100644 index 0f9a8a3bbdd5..000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_upgrade_modal.tsx +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useCallback, useEffect, useState } from 'react'; -import { - EuiButton, - EuiCheckbox, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiLink, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; - -import { - sendGetAgents, - sendGetOneAgentPolicy, - sendPutSettings, - useLink, - useStartServices, -} from '../../../hooks'; -import type { PackagePolicy } from '../../../types'; -import { FLEET_SERVER_PACKAGE } from '../../../constants'; - -interface Props { - onClose: () => void; -} - -export const FleetServerUpgradeModal: React.FunctionComponent = ({ onClose }) => { - const { getAssetsPath } = useLink(); - const { notifications, cloud, docLinks } = useStartServices(); - - const isCloud = !!cloud?.cloudId; - - const [checked, setChecked] = useState(false); - const [isAgentsAndPoliciesLoaded, setAgentsAndPoliciesLoaded] = useState(false); - - // Check if an other agent than the fleet server is already enrolled - useEffect(() => { - async function check() { - try { - const agentPoliciesAlreadyChecked: { [k: string]: boolean } = {}; - - const res = await sendGetAgents({ - page: 1, - perPage: 10, - showInactive: false, - }); - - if (res.error) { - throw res.error; - } - - for (const agent of res.data?.items ?? []) { - if (!agent.policy_id || agentPoliciesAlreadyChecked[agent.policy_id]) { - continue; - } - - agentPoliciesAlreadyChecked[agent.policy_id] = true; - const policyRes = await sendGetOneAgentPolicy(agent.policy_id); - const hasFleetServer = - (policyRes.data?.item.package_policies as PackagePolicy[]).some((p: PackagePolicy) => { - return p.package?.name === FLEET_SERVER_PACKAGE; - }) ?? false; - if (!hasFleetServer) { - await sendPutSettings({ - has_seen_fleet_migration_notice: true, - }); - onClose(); - return; - } - } - setAgentsAndPoliciesLoaded(true); - } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.fleet.fleetServerUpgradeModal.errorLoadingAgents', { - defaultMessage: `Error loading agents`, - }), - }); - } - } - - check(); - }, [notifications.toasts, onClose]); - - const onChange = useCallback(async () => { - try { - setChecked(!checked); - await sendPutSettings({ - has_seen_fleet_migration_notice: !checked, - }); - } catch (error) { - notifications.toasts.addError(error, { - title: i18n.translate('xpack.fleet.fleetServerUpgradeModal.failedUpdateTitle', { - defaultMessage: `Error saving settings`, - }), - }); - } - }, [checked, setChecked, notifications]); - - if (!isAgentsAndPoliciesLoaded) { - return null; - } - - return ( - - - - - - - - - - - {isCloud ? ( - - - - ), - }} - /> - ) : ( - - - - ), - link: ( - - - - ), - }} - /> - )} - - - - - - - ), - }} - /> - - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx index 7139cabed4f6..e30e4512e1de 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx @@ -51,9 +51,7 @@ describe('AgentApp', () => { mockedUseGetSettings.mockReturnValue({ isLoading: false, data: { - item: { - has_seen_fleet_migration_notice: true, - }, + item: {}, }, } as any); mockedUseAuthz.mockReturnValue({ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx index ed770311ad9e..0cf9e7d1ba6b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx @@ -5,28 +5,20 @@ * 2.0. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { Router, Route, Switch, useHistory } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FLEET_ROUTING_PATHS } from '../../constants'; import { Loading, Error } from '../../components'; -import { - useConfig, - useFleetStatus, - useBreadcrumbs, - useAuthz, - useGetSettings, - useFlyoutContext, -} from '../../hooks'; +import { useConfig, useFleetStatus, useBreadcrumbs, useAuthz, useFlyoutContext } from '../../hooks'; import { DefaultLayout, WithoutHeaderLayout } from '../../layouts'; import { AgentListPage } from './agent_list_page'; import { FleetServerRequirementPage, MissingESRequirementsPage } from './agent_requirements_page'; import { AgentDetailsPage } from './agent_details_page'; import { NoAccessPage } from './error_pages/no_access'; -import { FleetServerUpgradeModal } from './components/fleet_server_upgrade_modal'; export const AgentsApp: React.FunctionComponent = () => { useBreadcrumbs('agent_list'); @@ -36,20 +28,6 @@ export const AgentsApp: React.FunctionComponent = () => { const fleetStatus = useFleetStatus(); const flyoutContext = useFlyoutContext(); - const settings = useGetSettings(); - - const [fleetServerModalVisible, setFleetServerModalVisible] = useState(false); - const onCloseFleetServerModal = useCallback(() => { - setFleetServerModalVisible(false); - }, [setFleetServerModalVisible]); - - useEffect(() => { - // if it's undefined do not show the modal - if (settings.data && settings.data?.item.has_seen_fleet_migration_notice === false) { - setFleetServerModalVisible(true); - } - }, [settings.data]); - if (!agents.enabled) return null; if (!fleetStatus.missingRequirements && fleetStatus.isLoading) { return ; @@ -114,9 +92,6 @@ export const AgentsApp: React.FunctionComponent = () => { - {fleetServerModalVisible && ( - - )} {displayInstructions ? ( ) : ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx new file mode 100644 index 000000000000..2ea4a6775d6e --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; +import type { FunctionComponent, ReactElement } from 'react'; +import { EuiButton, EuiText, EuiTourStep } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +type TourType = 'addIntegrationButton' | 'integrationsList'; +const getTourConfig = (packageKey: string, tourType: TourType) => { + 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/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/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index 57b2b93fabb2..3a0033435ed9 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -143,6 +143,16 @@ export const sendInstallPackage = (pkgName: string, pkgVersion: string, force: b }); }; +export const sendBulkInstallPackages = (packages: string[]) => { + return sendRequest({ + path: epmRouteService.getBulkInstallPath(), + method: 'post', + body: { + packages, + }, + }); +}; + export const sendRemovePackage = (pkgName: string, pkgVersion: string, force: boolean = false) => { return sendRequest({ path: epmRouteService.getRemovePath(pkgName, pkgVersion), 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/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 55d38b00dec3..ed03e26b6453 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -46,6 +46,7 @@ import { migratePackagePolicyToV840, } from './migrations/to_v8_4_0'; import { migratePackagePolicyToV850, migrateAgentPolicyToV850 } from './migrations/to_v8_5_0'; +import { migrateSettingsToV860 } from './migrations/to_v8_6_0'; /* * Saved object types and mappings @@ -56,6 +57,7 @@ import { migratePackagePolicyToV850, migrateAgentPolicyToV850 } from './migratio const getSavedObjectTypes = ( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup ): { [key: string]: SavedObjectsType } => ({ + // Deprecated [GLOBAL_SETTINGS_SAVED_OBJECT_TYPE]: { name: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, hidden: false, @@ -67,12 +69,12 @@ const getSavedObjectTypes = ( properties: { fleet_server_hosts: { type: 'keyword' }, has_seen_add_data_notice: { type: 'boolean', index: false }, - has_seen_fleet_migration_notice: { type: 'boolean', index: false }, }, }, migrations: { '7.10.0': migrateSettingsToV7100, '7.13.0': migrateSettingsToV7130, + '8.6.0': migrateSettingsToV860, }, }, [AGENT_POLICY_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_6_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_6_0.ts new file mode 100644 index 000000000000..5134249ddd1e --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_6_0.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectMigrationFn } from '@kbn/core/server'; + +import type { Settings } from '../../../common/types'; + +export const migrateSettingsToV860: SavedObjectMigrationFn = ( + settingsDoc, + migrationContext +) => { + // @ts-expect-error has_seen_fleet_migration_notice property does not exists anymore + delete settingsDoc.attributes.has_seen_fleet_migration_notice; + + return settingsDoc; +}; 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 5beb5c0a9ac0..9169df19fbcf 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts @@ -331,6 +331,15 @@ describe('invalidateAPIKeysForAgents', () => { id: 'defaultApiKeyHistory2', }, ], + outputs: [ + { + api_key_id: 'outputApiKey1', + to_retire_api_key_ids: [{ id: 'outputApiKeyRetire1' }, { id: 'outputApiKeyRetire2' }], + }, + { + api_key_id: 'outputApiKey2', + }, + ], } as any, ]); @@ -340,6 +349,10 @@ describe('invalidateAPIKeysForAgents', () => { 'defaultApiKey1', 'defaultApiKeyHistory1', 'defaultApiKeyHistory2', + 'outputApiKey1', + 'outputApiKeyRetire1', + 'outputApiKeyRetire2', + 'outputApiKey2', ]); }); }); 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 c735254f1825..fed5d44fe98e 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 @@ -215,6 +215,16 @@ export async function invalidateAPIKeysForAgents(agents: Agent[]) { if (agent.default_api_key_history) { agent.default_api_key_history.forEach((apiKey) => keys.push(apiKey.id)); } + if (agent.outputs) { + 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)); + } + }); + } return keys; }, []); 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/types/rest_spec/settings.ts b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts index 678a71c3214c..4544b677cba8 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts @@ -23,7 +23,6 @@ export const PutSettingsRequestSchema = { }) ), has_seen_add_data_notice: schema.maybe(schema.boolean()), - has_seen_fleet_migration_notice: schema.maybe(schema.boolean()), additional_yaml_config: schema.maybe(schema.string()), // Deprecated not used kibana_urls: schema.maybe( 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/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index 74f2468eb4c4..e92ac801e861 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -8,8 +8,10 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import type { Query, TimeRange } from '@kbn/es-query'; -import React from 'react'; +import React, { useState } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { NoData } from '../../../../components/empty_states'; import { InfraClientStartDeps } from '../../../../types'; const getLensHostsTable = ( @@ -498,23 +500,54 @@ interface Props { timeRange: TimeRange; query: Query; searchSessionId: string; + onRefetch: () => void; + onLoading: (isLoading: boolean) => void; + isLensLoading: boolean; } export const HostsTable: React.FunctionComponent = ({ dataView, timeRange, query, searchSessionId, + onRefetch, + onLoading, + isLensLoading, }) => { const { services: { lens }, } = useKibana(); const LensComponent = lens?.EmbeddableComponent; + const [noData, setNoData] = useState(false); + + if (noData && !isLensLoading) { + return ( + + ); + } return ( { + if (!isLoading && adapters?.tables) { + setNoData(adapters?.tables.tables.default?.rows.length === 0); + onLoading(false); + } + }} /> ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx index 63e95a19f1c7..7bf087db39eb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx @@ -27,6 +27,7 @@ export const HostsContent: React.FunctionComponent = () => { useMetricsDataViewContext(); // needed to refresh the lens table when filters havent changed const [searchSessionId, setSearchSessionId] = useState(data.search.session.start()); + const [isLensLoading, setIsLensLoading] = useState(false); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { @@ -34,11 +35,26 @@ export const HostsContent: React.FunctionComponent = () => { if (payload.query) { setQuery(payload.query); } + setIsLensLoading(true); setSearchSessionId(data.search.session.start()); }, [setDateRange, setQuery, data.search.session] ); + const onLoading = useCallback( + (isLoading: boolean) => { + if (isLensLoading) { + setIsLensLoading(isLoading); + } + }, + [setIsLensLoading, isLensLoading] + ); + + const onRefetch = useCallback(() => { + setIsLensLoading(true); + setSearchSessionId(data.search.session.start()); + }, [data.search.session]); + return (

{metricsDataView ? ( @@ -61,6 +77,9 @@ export const HostsContent: React.FunctionComponent = () => { timeRange={dateRange} query={query} searchSessionId={searchSessionId} + onRefetch={onRefetch} + onLoading={onLoading} + isLensLoading={isLensLoading} /> ) : hasFailedCreatingDataView || hasFailedFetchingDataView ? ( 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..80c1384a1020 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([]); @@ -357,27 +356,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 +573,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 +616,7 @@ export const LensTopNavMenu = ({ indexPatterns, dataViews.indexPatterns, data.query.timefilter.timefilter, + isOnTextBasedMode, lensStore, theme$, ]); @@ -846,10 +837,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 +852,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/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/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/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 5a975482e625..84c56b0f914f 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 @@ -605,7 +605,15 @@ export function DimensionEditor(props: DimensionEditorProps) { ...services, }; - const helpButton = ; + const helpButton = ( + + ); const columnsSidebar = [ { @@ -639,6 +647,11 @@ export function DimensionEditor(props: DimensionEditorProps) { + + {i18n.translate('xpack.lens.indexPattern.functionsLabel', { + defaultMessage: 'Functions', + })} + - - {i18n.translate('xpack.lens.indexPattern.functionsLabel', { - defaultMessage: 'Functions', - })} - } fullWidth 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..c20454a9dfeb 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 @@ -112,7 +112,6 @@ function getTypeI18n(type: string) { return ''; } -// Todo: i18n everything here export const tinymathFunctions: Record< string, { @@ -527,6 +526,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/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/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/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() { `slo-${sloId}`; +export const getSLOTransformId = (sloId: string, sloRevision: number) => + `slo-${sloId}-${sloRevision}`; diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 63ef4e1e07e6..c80abf6b60b5 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -12,22 +12,23 @@ import { DefaultTransformManager, KibanaSavedObjectsSLORepository, GetSLO, + UpdateSLO, } from '../../services/slo'; - import { ApmTransactionDurationTransformGenerator, ApmTransactionErrorRateTransformGenerator, TransformGenerator, } from '../../services/slo/transform_generators'; -import { SLITypes } from '../../types/models'; +import { IndicatorTypes } from '../../types/models'; import { createSLOParamsSchema, deleteSLOParamsSchema, getSLOParamsSchema, + updateSLOParamsSchema, } from '../../types/rest_specs'; import { createObservabilityServerRoute } from '../create_observability_server_route'; -const transformGenerators: Record = { +const transformGenerators: Record = { 'slo.apm.transaction_duration': new ApmTransactionDurationTransformGenerator(), 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; @@ -53,6 +54,26 @@ const createSLORoute = createObservabilityServerRoute({ }, }); +const updateSLORoute = createObservabilityServerRoute({ + endpoint: 'PUT /api/observability/slos/{id}', + options: { + tags: [], + }, + params: updateSLOParamsSchema, + handler: async ({ context, params, logger }) => { + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const soClient = (await context.core).savedObjects.client; + + const repository = new KibanaSavedObjectsSLORepository(soClient); + const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger); + const updateSLO = new UpdateSLO(repository, transformManager, esClient); + + const response = await updateSLO.execute(params.path.id, params.body); + + return response; + }, +}); + const deleteSLORoute = createObservabilityServerRoute({ endpoint: 'DELETE /api/observability/slos/{id}', options: { @@ -89,4 +110,9 @@ const getSLORoute = createObservabilityServerRoute({ }, }); -export const slosRouteRepository = { ...createSLORoute, ...getSLORoute, ...deleteSLORoute }; +export const slosRouteRepository = { + ...createSLORoute, + ...updateSLORoute, + ...getSLORoute, + ...deleteSLORoute, +}; diff --git a/x-pack/plugins/observability/server/services/slo/create_slo.ts b/x-pack/plugins/observability/server/services/slo/create_slo.ts index 8a81228db96f..35a908a48bd2 100644 --- a/x-pack/plugins/observability/server/services/slo/create_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/create_slo.ts @@ -20,8 +20,8 @@ export class CreateSLO { private transformManager: TransformManager ) {} - public async execute(sloParams: CreateSLOParams): Promise { - const slo = this.toSLO(sloParams); + public async execute(params: CreateSLOParams): Promise { + const slo = this.toSLO(params); await this.resourceInstaller.ensureCommonResourcesInstalled(); await this.repository.save(slo); @@ -48,10 +48,14 @@ export class CreateSLO { return this.toResponse(slo); } - private toSLO(sloParams: CreateSLOParams): SLO { + private toSLO(params: CreateSLOParams): SLO { + const now = new Date(); return { - ...sloParams, + ...params, id: uuid.v1(), + revision: 1, + created_at: now, + updated_at: now, }; } diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts index 2e43c81f6d38..8353e0fa16f2 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts @@ -30,12 +30,17 @@ describe('DeleteSLO', () => { describe('happy path', () => { it('removes the transform, the roll up data and the SLO from the repository', async () => { const slo = createSLO(createAPMTransactionErrorRateIndicator()); + mockRepository.findById.mockResolvedValueOnce(slo); await deleteSLO.execute(slo.id); expect(mockRepository.findById).toHaveBeenCalledWith(slo.id); - expect(mockTransformManager.stop).toHaveBeenCalledWith(getSLOTransformId(slo.id)); - expect(mockTransformManager.uninstall).toHaveBeenCalledWith(getSLOTransformId(slo.id)); + expect(mockTransformManager.stop).toHaveBeenCalledWith( + getSLOTransformId(slo.id, slo.revision) + ); + expect(mockTransformManager.uninstall).toHaveBeenCalledWith( + getSLOTransformId(slo.id, slo.revision) + ); expect(mockEsClient.deleteByQuery).toHaveBeenCalledWith( expect.objectContaining({ index: `${SLO_INDEX_TEMPLATE_NAME}*`, diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.ts index a7d931174a59..8ec8d2060f73 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.ts @@ -19,15 +19,14 @@ export class DeleteSLO { ) {} public async execute(sloId: string): Promise { - // ensure the slo exists on the request's space. - await this.repository.findById(sloId); + const slo = await this.repository.findById(sloId); - const sloTransformId = getSLOTransformId(sloId); + const sloTransformId = getSLOTransformId(slo.id, slo.revision); await this.transformManager.stop(sloTransformId); await this.transformManager.uninstall(sloTransformId); - await this.deleteRollupData(sloId); - await this.repository.deleteById(sloId); + await this.deleteRollupData(slo.id); + await this.repository.deleteById(slo.id); } private async deleteRollupData(sloId: string): Promise { diff --git a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts index d9d4aa575666..9a193d9fb1cf 100644 --- a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts +++ b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts @@ -6,10 +6,15 @@ */ import uuid from 'uuid'; -import { SLI, SLO } from '../../../types/models'; +import { + APMTransactionDurationIndicator, + APMTransactionErrorRateIndicator, + Indicator, + SLO, +} from '../../../types/models'; import { CreateSLOParams } from '../../../types/rest_specs'; -const commonSLO: Omit = { +const defaultSLO: Omit = { name: 'irrelevant', description: 'irrelevant', time_window: { @@ -20,20 +25,29 @@ const commonSLO: Omit = { objective: { target: 0.999, }, + revision: 1, }; -export const createSLOParams = (indicator: SLI): CreateSLOParams => ({ - ...commonSLO, +export const createSLOParams = (indicator: Indicator): CreateSLOParams => ({ + ...defaultSLO, indicator, }); -export const createSLO = (indicator: SLI): SLO => ({ - ...commonSLO, - id: uuid.v1(), - indicator, -}); +export const createSLO = (indicator: Indicator): SLO => { + const now = new Date(); + return { + ...defaultSLO, + id: uuid.v1(), + indicator, + revision: 1, + created_at: now, + updated_at: now, + }; +}; -export const createAPMTransactionErrorRateIndicator = (params = {}): SLI => ({ +export const createAPMTransactionErrorRateIndicator = ( + params: Partial = {} +): Indicator => ({ type: 'slo.apm.transaction_error_rate', params: { environment: 'irrelevant', @@ -45,7 +59,9 @@ export const createAPMTransactionErrorRateIndicator = (params = {}): SLI => ({ }, }); -export const createAPMTransactionDurationIndicator = (params = {}): SLI => ({ +export const createAPMTransactionDurationIndicator = ( + params: Partial = {} +): Indicator => ({ type: 'slo.apm.transaction_duration', params: { environment: 'irrelevant', diff --git a/x-pack/plugins/observability/server/services/slo/get_slo.test.ts b/x-pack/plugins/observability/server/services/slo/get_slo.test.ts index 768424d6b9ee..dac1b7aa8ca2 100644 --- a/x-pack/plugins/observability/server/services/slo/get_slo.test.ts +++ b/x-pack/plugins/observability/server/services/slo/get_slo.test.ts @@ -49,6 +49,9 @@ describe('GetSLO', () => { duration: '7d', is_rolling: true, }, + created_at: slo.created_at, + updated_at: slo.updated_at, + revision: slo.revision, }); }); }); diff --git a/x-pack/plugins/observability/server/services/slo/get_slo.ts b/x-pack/plugins/observability/server/services/slo/get_slo.ts index 1730dec46496..26ae0ee51184 100644 --- a/x-pack/plugins/observability/server/services/slo/get_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/get_slo.ts @@ -26,6 +26,9 @@ export class GetSLO { time_window: slo.time_window, budgeting_method: slo.budgeting_method, objective: slo.objective, + revision: slo.revision, + created_at: slo.created_at, + updated_at: slo.updated_at, }; } } diff --git a/x-pack/plugins/observability/server/services/slo/index.ts b/x-pack/plugins/observability/server/services/slo/index.ts index 7515262628f0..58b31e424842 100644 --- a/x-pack/plugins/observability/server/services/slo/index.ts +++ b/x-pack/plugins/observability/server/services/slo/index.ts @@ -11,3 +11,4 @@ export * from './transform_manager'; export * from './create_slo'; export * from './delete_slo'; export * from './get_slo'; +export * from './update_slo'; diff --git a/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts b/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts index 8c8e38285f1f..fe0ebe406ef3 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts @@ -20,11 +20,7 @@ const SOME_SLO = createSLO(createAPMTransactionDurationIndicator()); function aStoredSLO(slo: SLO): SavedObject { return { id: slo.id, - attributes: { - ...slo, - updated_at: new Date().toISOString(), - created_at: new Date().toISOString(), - }, + attributes: slo, type: SO_SLO_TYPE, references: [], }; @@ -73,7 +69,7 @@ describe('KibanaSavedObjectsSLORepository', () => { updated_at: expect.anything(), created_at: expect.anything(), }), - { id: SOME_SLO.id } + { id: SOME_SLO.id, overwrite: true } ); }); diff --git a/x-pack/plugins/observability/server/services/slo/slo_repository.ts b/x-pack/plugins/observability/server/services/slo/slo_repository.ts index 29e16346fcdb..8a8abcd83465 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_repository.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_repository.ts @@ -22,24 +22,18 @@ export class KibanaSavedObjectsSLORepository implements SLORepository { constructor(private soClient: SavedObjectsClientContract) {} async save(slo: SLO): Promise { - const now = new Date().toISOString(); - const savedSLO = await this.soClient.create( - SO_SLO_TYPE, - { - ...slo, - created_at: now, - updated_at: now, - }, - { id: slo.id } - ); + const savedSLO = await this.soClient.create(SO_SLO_TYPE, slo, { + id: slo.id, + overwrite: true, + }); - return toSLOModel(savedSLO.attributes); + return savedSLO.attributes; } async findById(id: string): Promise { try { const slo = await this.soClient.get(SO_SLO_TYPE, id); - return toSLOModel(slo.attributes); + return slo.attributes; } catch (err) { if (SavedObjectsErrorHelpers.isNotFoundError(err)) { throw new SLONotFound(`SLO [${id}] not found`); @@ -59,15 +53,3 @@ export class KibanaSavedObjectsSLORepository implements SLORepository { } } } - -function toSLOModel(slo: StoredSLO): SLO { - return { - id: slo.id, - name: slo.name, - description: slo.description, - indicator: slo.indicator, - time_window: slo.time_window, - budgeting_method: slo.budgeting_method, - objective: slo.objective, - }; -} diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap index 7b7f49061fd5..d9a9c5852192 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap @@ -82,6 +82,11 @@ Object { "field": "slo.id", }, }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, }, }, "settings": Object { @@ -127,6 +132,12 @@ Object { }, "type": "keyword", }, + "slo.revision": Object { + "script": Object { + "source": "emit(1)", + }, + "type": "long", + }, }, }, "sync": Object { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap index 8b73f76a8082..d59faee05b93 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap @@ -87,6 +87,11 @@ Object { "field": "slo.id", }, }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, }, }, "settings": Object { @@ -132,6 +137,12 @@ Object { }, "type": "keyword", }, + "slo.revision": Object { + "script": Object { + "source": "emit(1)", + }, + "type": "long", + }, }, }, "sync": Object { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts index ba984e542619..08d70bbe831a 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts @@ -19,10 +19,13 @@ describe('APM Transaction Duration Transform Generator', () => { transform_id: expect.any(String), source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } }, }); - expect(transform.transform_id).toEqual(`slo-${anSLO.id}`); + expect(transform.transform_id).toEqual(`slo-${anSLO.id}-${anSLO.revision}`); expect(transform.source.runtime_mappings!['slo.id']).toMatchObject({ script: { source: `emit('${anSLO.id}')` }, }); + expect(transform.source.runtime_mappings!['slo.revision']).toMatchObject({ + script: { source: `emit(${anSLO.revision})` }, + }); }); it("does not include the query filter when params are '*'", async () => { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts index bc45e12abbb3..061f020bab2f 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts @@ -10,71 +10,67 @@ import { MappingRuntimeFieldType, TransformPutTransformRequest, } from '@elastic/elasticsearch/lib/api/types'; -import { ALL_VALUE } from '../../../types/schema'; +import { ALL_VALUE, apmTransactionDurationIndicatorSchema } from '../../../types/schema'; import { SLO_DESTINATION_INDEX_NAME, SLO_INGEST_PIPELINE_NAME, getSLOTransformId, } from '../../../assets/constants'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; -import { - SLO, - apmTransactionDurationSLOSchema, - APMTransactionDurationSLO, -} from '../../../types/models'; +import { SLO, APMTransactionDurationIndicator } from '../../../types/models'; import { TransformGenerator } from '.'; const APM_SOURCE_INDEX = 'metrics-apm*'; export class ApmTransactionDurationTransformGenerator implements TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { - if (!apmTransactionDurationSLOSchema.is(slo)) { + if (!apmTransactionDurationIndicatorSchema.is(slo.indicator)) { throw new Error(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } return getSLOTransformTemplate( this.buildTransformId(slo), - this.buildSource(slo), + this.buildSource(slo, slo.indicator), this.buildDestination(), this.buildGroupBy(), - this.buildAggregations(slo) + this.buildAggregations(slo.indicator) ); } - private buildTransformId(slo: APMTransactionDurationSLO): string { - return getSLOTransformId(slo.id); + private buildTransformId(slo: SLO): string { + return getSLOTransformId(slo.id, slo.revision); } - private buildSource(slo: APMTransactionDurationSLO) { + private buildSource(slo: SLO, indicator: APMTransactionDurationIndicator) { const queryFilter = []; - if (slo.indicator.params.service !== ALL_VALUE) { + if (indicator.params.service !== ALL_VALUE) { queryFilter.push({ match: { - 'service.name': slo.indicator.params.service, + 'service.name': indicator.params.service, }, }); } - if (slo.indicator.params.environment !== ALL_VALUE) { + if (indicator.params.environment !== ALL_VALUE) { queryFilter.push({ match: { - 'service.environment': slo.indicator.params.environment, + 'service.environment': indicator.params.environment, }, }); } - if (slo.indicator.params.transaction_name !== ALL_VALUE) { + if (indicator.params.transaction_name !== ALL_VALUE) { queryFilter.push({ match: { - 'transaction.name': slo.indicator.params.transaction_name, + 'transaction.name': indicator.params.transaction_name, }, }); } - if (slo.indicator.params.transaction_type !== ALL_VALUE) { + if (indicator.params.transaction_type !== ALL_VALUE) { queryFilter.push({ match: { - 'transaction.type': slo.indicator.params.transaction_type, + 'transaction.type': indicator.params.transaction_type, }, }); } @@ -88,6 +84,12 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera source: `emit('${slo.id}')`, }, }, + 'slo.revision': { + type: 'long' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.revision})`, + }, + }, }, query: { bool: { @@ -118,6 +120,11 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera field: 'slo.id', }, }, + 'slo.revision': { + terms: { + field: 'slo.revision', + }, + }, '@timestamp': { date_histogram: { field: '@timestamp', @@ -147,8 +154,8 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera }; } - private buildAggregations(slo: APMTransactionDurationSLO) { - const truncatedThreshold = Math.trunc(slo.indicator.params['threshold.us']); + private buildAggregations(indicator: APMTransactionDurationIndicator) { + const truncatedThreshold = Math.trunc(indicator.params['threshold.us']); return { _numerator: { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts index 2bc88c576f8c..e5f705b985d4 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts @@ -19,10 +19,13 @@ describe('APM Transaction Error Rate Transform Generator', () => { transform_id: expect.any(String), source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } }, }); - expect(transform.transform_id).toEqual(`slo-${anSLO.id}`); + expect(transform.transform_id).toEqual(`slo-${anSLO.id}-${anSLO.revision}`); expect(transform.source.runtime_mappings!['slo.id']).toMatchObject({ script: { source: `emit('${anSLO.id}')` }, }); + expect(transform.source.runtime_mappings!['slo.revision']).toMatchObject({ + script: { source: `emit(${anSLO.revision})` }, + }); }); it("uses default values when 'good_status_codes' is not specified", async () => { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts index 23a9a03f6e14..e9a796d67e36 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts @@ -10,7 +10,7 @@ import { MappingRuntimeFieldType, TransformPutTransformRequest, } from '@elastic/elasticsearch/lib/api/types'; -import { ALL_VALUE } from '../../../types/schema'; +import { ALL_VALUE, apmTransactionErrorRateIndicatorSchema } from '../../../types/schema'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; import { TransformGenerator } from '.'; import { @@ -18,11 +18,7 @@ import { SLO_INGEST_PIPELINE_NAME, getSLOTransformId, } from '../../../assets/constants'; -import { - apmTransactionErrorRateSLOSchema, - APMTransactionErrorRateSLO, - SLO, -} from '../../../types/models'; +import { APMTransactionErrorRateIndicator, SLO } from '../../../types/models'; const APM_SOURCE_INDEX = 'metrics-apm*'; const ALLOWED_STATUS_CODES = ['2xx', '3xx', '4xx', '5xx']; @@ -30,53 +26,53 @@ const DEFAULT_GOOD_STATUS_CODES = ['2xx', '3xx', '4xx']; export class ApmTransactionErrorRateTransformGenerator implements TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { - if (!apmTransactionErrorRateSLOSchema.is(slo)) { + if (!apmTransactionErrorRateIndicatorSchema.is(slo.indicator)) { throw new Error(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } return getSLOTransformTemplate( this.buildTransformId(slo), - this.buildSource(slo), + this.buildSource(slo, slo.indicator), this.buildDestination(), this.buildGroupBy(), - this.buildAggregations(slo) + this.buildAggregations(slo, slo.indicator) ); } - private buildTransformId(slo: APMTransactionErrorRateSLO): string { - return getSLOTransformId(slo.id); + private buildTransformId(slo: SLO): string { + return getSLOTransformId(slo.id, slo.revision); } - private buildSource(slo: APMTransactionErrorRateSLO) { + private buildSource(slo: SLO, indicator: APMTransactionErrorRateIndicator) { const queryFilter = []; - if (slo.indicator.params.service !== ALL_VALUE) { + if (indicator.params.service !== ALL_VALUE) { queryFilter.push({ match: { - 'service.name': slo.indicator.params.service, + 'service.name': indicator.params.service, }, }); } - if (slo.indicator.params.environment !== ALL_VALUE) { + if (indicator.params.environment !== ALL_VALUE) { queryFilter.push({ match: { - 'service.environment': slo.indicator.params.environment, + 'service.environment': indicator.params.environment, }, }); } - if (slo.indicator.params.transaction_name !== ALL_VALUE) { + if (indicator.params.transaction_name !== ALL_VALUE) { queryFilter.push({ match: { - 'transaction.name': slo.indicator.params.transaction_name, + 'transaction.name': indicator.params.transaction_name, }, }); } - if (slo.indicator.params.transaction_type !== ALL_VALUE) { + if (indicator.params.transaction_type !== ALL_VALUE) { queryFilter.push({ match: { - 'transaction.type': slo.indicator.params.transaction_type, + 'transaction.type': indicator.params.transaction_type, }, }); } @@ -90,6 +86,12 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener source: `emit('${slo.id}')`, }, }, + 'slo.revision': { + type: 'long' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.revision})`, + }, + }, }, query: { bool: { @@ -120,6 +122,11 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener field: 'slo.id', }, }, + 'slo.revision': { + terms: { + field: 'slo.revision', + }, + }, '@timestamp': { date_histogram: { field: '@timestamp', @@ -149,10 +156,8 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener }; } - private buildAggregations(slo: APMTransactionErrorRateSLO) { - const goodStatusCodesFilter = this.getGoodStatusCodesFilter( - slo.indicator.params.good_status_codes - ); + private buildAggregations(slo: SLO, indicator: APMTransactionErrorRateIndicator) { + const goodStatusCodesFilter = this.getGoodStatusCodesFilter(indicator.params.good_status_codes); return { 'slo.numerator': { diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts index 434e6841ff0e..dc67c6e1485c 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts @@ -20,7 +20,7 @@ import { ApmTransactionErrorRateTransformGenerator, TransformGenerator, } from './transform_generators'; -import { SLO, SLITypes } from '../../types/models'; +import { SLO, IndicatorTypes } from '../../types/models'; import { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo'; describe('TransformManager', () => { @@ -36,7 +36,7 @@ describe('TransformManager', () => { describe('Unhappy path', () => { it('throws when no generator exists for the slo indicator type', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { + const generators: Record = { 'slo.apm.transaction_duration': new DummyTransformGenerator(), }; const service = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -58,7 +58,7 @@ describe('TransformManager', () => { it('throws when transform generator fails', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { + const generators: Record = { 'slo.apm.transaction_duration': new FailTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -82,7 +82,7 @@ describe('TransformManager', () => { it('installs the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { + const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -91,14 +91,14 @@ describe('TransformManager', () => { const transformId = await transformManager.install(slo); expect(esClientMock.transform.putTransform).toHaveBeenCalledTimes(1); - expect(transformId).toBe(`slo-${slo.id}`); + expect(transformId).toBe(`slo-${slo.id}-${slo.revision}`); }); }); describe('Start', () => { it('starts the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { + const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -112,7 +112,7 @@ describe('TransformManager', () => { describe('Stop', () => { it('stops the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { + const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -126,7 +126,7 @@ describe('TransformManager', () => { describe('Uninstall', () => { it('uninstalls the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { + const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -141,7 +141,7 @@ describe('TransformManager', () => { new EsErrors.ConnectionError('irrelevant') ); // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { + const generators: Record = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.ts index 154660fccaf9..66c4f972ad48 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; -import { SLO, SLITypes } from '../../types/models'; +import { SLO, IndicatorTypes } from '../../types/models'; import { retryTransientEsErrors } from '../../utils/retry'; import { TransformGenerator } from './transform_generators'; @@ -22,7 +22,7 @@ export interface TransformManager { export class DefaultTransformManager implements TransformManager { constructor( - private generators: Record, + private generators: Record, private esClient: ElasticsearchClient, private logger: Logger ) {} diff --git a/x-pack/plugins/observability/server/services/slo/update_slo.test.ts b/x-pack/plugins/observability/server/services/slo/update_slo.test.ts new file mode 100644 index 000000000000..6964887fe914 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/update_slo.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; +import { getSLOTransformId } from '../../assets/constants'; +import { SLO } from '../../types/models'; +import { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo'; +import { createSLORepositoryMock, createTransformManagerMock } from './mocks'; +import { SLORepository } from './slo_repository'; +import { TransformManager } from './transform_manager'; +import { UpdateSLO } from './update_slo'; + +describe('UpdateSLO', () => { + let mockRepository: jest.Mocked; + let mockTransformManager: jest.Mocked; + let mockEsClient: jest.Mocked; + let updateSLO: UpdateSLO; + + beforeEach(() => { + mockRepository = createSLORepositoryMock(); + mockTransformManager = createTransformManagerMock(); + mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + updateSLO = new UpdateSLO(mockRepository, mockTransformManager, mockEsClient); + }); + + describe('without breaking changes', () => { + it('updates the SLO saved object without revision bump', async () => { + const slo = createSLO(createAPMTransactionErrorRateIndicator()); + mockRepository.findById.mockResolvedValueOnce(slo); + + const newName = 'new slo name'; + const response = await updateSLO.execute(slo.id, { name: newName }); + + expectTransformManagerNeverCalled(); + expect(mockEsClient.deleteByQuery).not.toBeCalled(); + expect(mockRepository.save).toBeCalledWith( + expect.objectContaining({ ...slo, name: newName, updated_at: expect.anything() }) + ); + expect(response.name).toBe(newName); + expect(response.updated_at).not.toBe(slo.updated_at); + }); + }); + + describe('with breaking changes', () => { + it('removes the obsolete data from the SLO previous revision', async () => { + const slo = createSLO(createAPMTransactionErrorRateIndicator({ environment: 'development' })); + mockRepository.findById.mockResolvedValueOnce(slo); + + const newIndicator = createAPMTransactionErrorRateIndicator({ environment: 'production' }); + await updateSLO.execute(slo.id, { indicator: newIndicator }); + + expectDeletionOfObsoleteSLOData(slo); + expect(mockRepository.save).toBeCalledWith( + expect.objectContaining({ + ...slo, + indicator: newIndicator, + revision: 2, + updated_at: expect.anything(), + }) + ); + expectInstallationOfNewSLOTransform(); + }); + }); + + function expectTransformManagerNeverCalled() { + expect(mockTransformManager.stop).not.toBeCalled(); + expect(mockTransformManager.uninstall).not.toBeCalled(); + expect(mockTransformManager.start).not.toBeCalled(); + expect(mockTransformManager.install).not.toBeCalled(); + } + + function expectInstallationOfNewSLOTransform() { + expect(mockTransformManager.start).toBeCalled(); + expect(mockTransformManager.install).toBeCalled(); + } + + function expectDeletionOfObsoleteSLOData(originalSlo: SLO) { + const transformId = getSLOTransformId(originalSlo.id, originalSlo.revision); + expect(mockTransformManager.stop).toBeCalledWith(transformId); + expect(mockTransformManager.uninstall).toBeCalledWith(transformId); + expect(mockEsClient.deleteByQuery).toBeCalledWith( + expect.objectContaining({ + query: { + bool: { + filter: [ + { term: { 'slo.id': originalSlo.id } }, + { term: { 'slo.revision': originalSlo.revision } }, + ], + }, + }, + }) + ); + } +}); diff --git a/x-pack/plugins/observability/server/services/slo/update_slo.ts b/x-pack/plugins/observability/server/services/slo/update_slo.ts new file mode 100644 index 000000000000..cee487c09a39 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/update_slo.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import deepEqual from 'fast-deep-equal'; +import { ElasticsearchClient } from '@kbn/core/server'; + +import { getSLOTransformId, SLO_INDEX_TEMPLATE_NAME } from '../../assets/constants'; +import { UpdateSLOParams, UpdateSLOResponse } from '../../types/rest_specs'; +import { SLORepository } from './slo_repository'; +import { TransformManager } from './transform_manager'; +import { SLO } from '../../types/models'; + +export class UpdateSLO { + constructor( + private repository: SLORepository, + private transformManager: TransformManager, + private esClient: ElasticsearchClient + ) {} + + public async execute(sloId: string, params: UpdateSLOParams): Promise { + const originalSlo = await this.repository.findById(sloId); + const { hasBreakingChange, updatedSlo } = this.updateSLO(originalSlo, params); + + if (hasBreakingChange) { + await this.deleteObsoleteSLORevisionData(originalSlo); + + await this.repository.save(updatedSlo); + await this.transformManager.install(updatedSlo); + await this.transformManager.start(getSLOTransformId(updatedSlo.id, updatedSlo.revision)); + } else { + await this.repository.save(updatedSlo); + } + + return this.toResponse(updatedSlo); + } + + private updateSLO(originalSlo: SLO, params: UpdateSLOParams) { + let hasBreakingChange = false; + const updatedSlo: SLO = Object.assign({}, originalSlo, params, { updated_at: new Date() }); + + if (!deepEqual(originalSlo.indicator, updatedSlo.indicator)) { + hasBreakingChange = true; + } + + if (hasBreakingChange) { + updatedSlo.revision++; + } + + return { hasBreakingChange, updatedSlo }; + } + + private async deleteObsoleteSLORevisionData(originalSlo: SLO) { + const originalSloTransformId = getSLOTransformId(originalSlo.id, originalSlo.revision); + await this.transformManager.stop(originalSloTransformId); + await this.transformManager.uninstall(originalSloTransformId); + await this.deleteRollupData(originalSlo.id, originalSlo.revision); + } + + private async deleteRollupData(sloId: string, sloRevision: number): Promise { + await this.esClient.deleteByQuery({ + index: `${SLO_INDEX_TEMPLATE_NAME}*`, + wait_for_completion: false, + query: { + bool: { + filter: [{ term: { 'slo.id': sloId } }, { term: { 'slo.revision': sloRevision } }], + }, + }, + }); + } + + private toResponse(slo: SLO): UpdateSLOResponse { + return { + id: slo.id, + name: slo.name, + description: slo.description, + indicator: slo.indicator, + budgeting_method: slo.budgeting_method, + time_window: slo.time_window, + objective: slo.objective, + created_at: slo.created_at, + updated_at: slo.updated_at, + }; + } +} diff --git a/x-pack/plugins/observability/server/types/models/slo.ts b/x-pack/plugins/observability/server/types/models/slo.ts index 1e30c4972f99..2c4e1ad64f60 100644 --- a/x-pack/plugins/observability/server/types/models/slo.ts +++ b/x-pack/plugins/observability/server/types/models/slo.ts @@ -9,48 +9,45 @@ import * as t from 'io-ts'; import { apmTransactionDurationIndicatorSchema, apmTransactionErrorRateIndicatorSchema, + budgetingMethodSchema, + dateType, indicatorSchema, indicatorTypesSchema, - commonSLOSchema, + objectiveSchema, + rollingTimeWindowSchema, } from '../schema'; -const sloSchema = t.intersection([ - commonSLOSchema, - t.type({ - id: t.string, - }), -]); +const sloSchema = t.type({ + id: t.string, + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + revision: t.number, + created_at: dateType, + updated_at: dateType, +}); -const apmTransactionErrorRateSLOSchema = t.intersection([ - sloSchema, - t.type({ indicator: apmTransactionErrorRateIndicatorSchema }), -]); +const storedSLOSchema = sloSchema; -const apmTransactionDurationSLOSchema = t.intersection([ - sloSchema, - t.type({ indicator: apmTransactionDurationIndicatorSchema }), -]); - -const storedSLOSchema = t.intersection([ - sloSchema, - t.type({ created_at: t.string, updated_at: t.string }), -]); +export { sloSchema, storedSLOSchema }; type SLO = t.TypeOf; -type APMTransactionErrorRateSLO = t.TypeOf; -type APMTransactionDurationSLO = t.TypeOf; -type SLI = t.TypeOf; -type SLITypes = t.TypeOf; +type APMTransactionErrorRateIndicator = t.TypeOf; +type APMTransactionDurationIndicator = t.TypeOf; +type Indicator = t.TypeOf; +type IndicatorTypes = t.TypeOf; type StoredSLO = t.TypeOf; -export { apmTransactionDurationSLOSchema, apmTransactionErrorRateSLOSchema }; export type { SLO, - APMTransactionErrorRateSLO, - APMTransactionDurationSLO, - SLI, - SLITypes, + Indicator, + IndicatorTypes, + APMTransactionErrorRateIndicator, + APMTransactionDurationIndicator, StoredSLO, }; diff --git a/x-pack/plugins/observability/server/types/rest_specs/slo.ts b/x-pack/plugins/observability/server/types/rest_specs/slo.ts index 7ee912cfc330..fe8905f41e9a 100644 --- a/x-pack/plugins/observability/server/types/rest_specs/slo.ts +++ b/x-pack/plugins/observability/server/types/rest_specs/slo.ts @@ -6,10 +6,18 @@ */ import * as t from 'io-ts'; -import { commonSLOSchema } from '../schema'; +import { dateType, indicatorSchema } from '../schema'; +import { budgetingMethodSchema, objectiveSchema, rollingTimeWindowSchema } from '../schema/slo'; const createSLOParamsSchema = t.type({ - body: commonSLOSchema, + body: t.type({ + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + }), }); const createSLOResponseSchema = t.type({ @@ -28,11 +36,56 @@ const getSLOParamsSchema = t.type({ }), }); -const getSLOResponseSchema = t.intersection([t.type({ id: t.string }), commonSLOSchema]); +const getSLOResponseSchema = t.type({ + id: t.string, + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + revision: t.number, + created_at: dateType, + updated_at: dateType, +}); + +const updateSLOParamsSchema = t.type({ + path: t.type({ + id: t.string, + }), + body: t.partial({ + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + }), +}); + +const updateSLOResponseSchema = t.type({ + id: t.string, + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + created_at: dateType, + updated_at: dateType, +}); type CreateSLOParams = t.TypeOf; type CreateSLOResponse = t.TypeOf; type GetSLOResponse = t.TypeOf; +type UpdateSLOParams = t.TypeOf; +type UpdateSLOResponse = t.TypeOf; -export { createSLOParamsSchema, deleteSLOParamsSchema, getSLOParamsSchema }; -export type { CreateSLOParams, CreateSLOResponse, GetSLOResponse }; +export { createSLOParamsSchema, deleteSLOParamsSchema, getSLOParamsSchema, updateSLOParamsSchema }; +export type { + CreateSLOParams, + CreateSLOResponse, + GetSLOResponse, + UpdateSLOParams, + UpdateSLOResponse, +}; diff --git a/x-pack/plugins/observability/server/types/schema/common.ts b/x-pack/plugins/observability/server/types/schema/common.ts index 02c8167b21fe..521b729f6678 100644 --- a/x-pack/plugins/observability/server/types/schema/common.ts +++ b/x-pack/plugins/observability/server/types/schema/common.ts @@ -5,10 +5,22 @@ * 2.0. */ +import { either } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; const ALL_VALUE = '*'; const allOrAnyString = t.union([t.literal(ALL_VALUE), t.string]); -export { allOrAnyString, ALL_VALUE }; +const dateType = new t.Type( + 'DateTime', + (input: unknown): input is Date => input instanceof Date, + (input: unknown, context: t.Context) => + either.chain(t.string.validate(input, context), (value: string) => { + const decoded = new Date(value); + return isNaN(decoded.getTime()) ? t.failure(input, context) : t.success(decoded); + }), + (date: Date): string => date.toISOString() +); + +export { allOrAnyString, ALL_VALUE, dateType }; diff --git a/x-pack/plugins/observability/server/types/schema/indicators.ts b/x-pack/plugins/observability/server/types/schema/indicators.ts index 4717c5fc915a..2b9c06590b1a 100644 --- a/x-pack/plugins/observability/server/types/schema/indicators.ts +++ b/x-pack/plugins/observability/server/types/schema/indicators.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { allOrAnyString } from './common'; -const apmTransactionDurationIndicatorTypeSchema = t.literal('slo.apm.transaction_duration'); +const apmTransactionDurationIndicatorTypeSchema = t.literal('slo.apm.transaction_duration'); const apmTransactionDurationIndicatorSchema = t.type({ type: apmTransactionDurationIndicatorTypeSchema, @@ -21,7 +21,9 @@ const apmTransactionDurationIndicatorSchema = t.type({ }), }); -const apmTransactionErrorRateIndicatorTypeSchema = t.literal('slo.apm.transaction_error_rate'); +const apmTransactionErrorRateIndicatorTypeSchema = t.literal( + 'slo.apm.transaction_error_rate' +); const apmTransactionErrorRateIndicatorSchema = t.type({ type: apmTransactionErrorRateIndicatorTypeSchema, diff --git a/x-pack/plugins/observability/server/types/schema/slo.ts b/x-pack/plugins/observability/server/types/schema/slo.ts index 6f92bbe3d29d..78389f742ea9 100644 --- a/x-pack/plugins/observability/server/types/schema/slo.ts +++ b/x-pack/plugins/observability/server/types/schema/slo.ts @@ -7,11 +7,9 @@ import * as t from 'io-ts'; -import { indicatorSchema } from './indicators'; - const rollingTimeWindowSchema = t.type({ duration: t.string, - is_rolling: t.literal(true), + is_rolling: t.literal(true), }); const budgetingMethodSchema = t.literal('occurrences'); @@ -20,13 +18,4 @@ const objectiveSchema = t.type({ target: t.number, }); -const commonSLOSchema = t.type({ - name: t.string, - description: t.string, - indicator: indicatorSchema, - time_window: rollingTimeWindowSchema, - budgeting_method: budgetingMethodSchema, - objective: objectiveSchema, -}); - -export { commonSLOSchema, rollingTimeWindowSchema, budgetingMethodSchema, objectiveSchema }; +export { rollingTimeWindowSchema, budgetingMethodSchema, objectiveSchema }; diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/index.tsx index 0fec230a3500..cd7a7b182772 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/index.tsx @@ -9,12 +9,11 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useState } from 're import { EuiSpacer } from '@elastic/eui'; import uuid from 'uuid'; import { useForm as useHookForm, FormProvider } from 'react-hook-form'; -import { get, isEmpty, map } from 'lodash'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { get, isEmpty, map, omit } from 'lodash'; import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import { QueryPackSelectable } from '../../live_queries/form/query_pack_selectable'; -import { useFormContext } from '../../shared_imports'; +import { useFormContext, useFormData } from '../../shared_imports'; import type { ArrayItem } from '../../shared_imports'; import { useKibana } from '../../common/lib/kibana'; import { LiveQueryQueryField } from '../../live_queries/form/live_query_query_field'; @@ -50,19 +49,25 @@ const OsqueryResponseActionParamsFormComponent = forwardRef< ResponseActionValidatorRef, OsqueryResponseActionsParamsFormProps >(({ item }, ref) => { + const { updateFieldValues } = useFormContext(); + const [data] = useFormData({ watch: [item.path] }); + const { params: defaultParams } = get(data, item.path); const uniqueId = useMemo(() => uuid.v4(), []); const hooksForm = useHookForm({ - defaultValues: { - ecs_mapping: {}, - id: uniqueId, - }, + defaultValues: defaultParams + ? { + ...omit(defaultParams, ['ecsMapping', 'packId']), + ecs_mapping: defaultParams.ecsMapping ?? {}, + packId: [defaultParams.packId] ?? [], + } + : { + ecs_mapping: {}, + id: uniqueId, + }, }); - const { watch, setValue, register, clearErrors, formState, handleSubmit } = hooksForm; + const { watch, register, formState, handleSubmit, reset } = hooksForm; const { errors, isValid } = formState; - const context = useFormContext(); - const data = context.getFormData(); - const { params: defaultParams } = get(data, item.path); const watchedValues = watch(); const { data: packData } = usePack({ @@ -72,51 +77,37 @@ const OsqueryResponseActionParamsFormComponent = forwardRef< const [queryType, setQueryType] = useState( !isEmpty(defaultParams?.queries) ? 'pack' : 'query' ); - const onSubmit = useCallback(async () => { - try { - if (queryType === 'pack') { - context.updateFieldValues({ - [item.path]: { - actionTypeId: OSQUERY_TYPE, - params: { - id: watchedValues.id, - packId: watchedValues?.packId?.length ? watchedValues?.packId[0] : undefined, - queries: packData - ? map(packData.queries, (query, queryId: string) => ({ - ...query, - id: queryId, - })) - : watchedValues.queries, - }, - }, - }); - } else { - context.updateFieldValues({ - [item.path]: { - actionTypeId: OSQUERY_TYPE, - params: { - id: watchedValues.id, - savedQueryId: watchedValues.savedQueryId, - query: watchedValues.query, - ecsMapping: watchedValues.ecs_mapping, - }, - }, - }); - } - // eslint-disable-next-line no-empty - } catch (e) {} - }, [ - context, - item.path, - packData, - queryType, - watchedValues.ecs_mapping, - watchedValues.id, - watchedValues?.packId, - watchedValues.queries, - watchedValues.query, - watchedValues.savedQueryId, - ]); + const onSubmit = useCallback( + async (formData) => { + updateFieldValues({ + [item.path]: + queryType === 'pack' + ? { + actionTypeId: OSQUERY_TYPE, + params: { + id: formData.id, + packId: formData?.packId?.length ? formData?.packId[0] : undefined, + queries: packData + ? map(packData.queries, (query, queryId: string) => ({ + ...query, + id: queryId, + })) + : formData.queries, + }, + } + : { + actionTypeId: OSQUERY_TYPE, + params: { + id: formData.id, + savedQueryId: formData.savedQueryId, + query: formData.query, + ecsMapping: formData.ecs_mapping, + }, + }, + }); + }, + [updateFieldValues, item.path, packData, queryType] + ); useEffect(() => { // @ts-expect-error update types @@ -135,41 +126,20 @@ const OsqueryResponseActionParamsFormComponent = forwardRef< register('id'); }, [register]); - const permissions = useKibana().services.application.capabilities.osquery; - - useEffectOnce(() => { - if (defaultParams && defaultParams.id) { - const { packId, ecsMapping, ...restParams } = defaultParams; - // TODO change map into forEach, and type defaultParams - map(restParams, (value, key: keyof OsqueryResponseActionsParamsFormFields) => { - if (!isEmpty(value)) { - setValue(key, value); - } - }); - - if (!isEmpty(ecsMapping)) { - setValue('ecs_mapping', ecsMapping); - } + useEffect(() => { + const subscription = watch(() => handleSubmit(onSubmit)()); - if (!isEmpty(packId)) { - setValue('packId', [packId]); - } - } - }); + return () => subscription.unsubscribe(); + }, [handleSubmit, onSubmit, watch]); - const resetFormFields = useCallback(() => { - setValue('packId', []); - setValue('savedQueryId', ''); - setValue('query', ''); - setValue('ecs_mapping', {}); - clearErrors(); - }, [clearErrors, setValue]); + const permissions = useKibana().services.application.capabilities.osquery; const canRunPacks = useMemo( () => !!((permissions.runSavedQueries || permissions.writeLiveQueries) && permissions.readPacks), [permissions] ); + const canRunSingleQuery = useMemo( () => !!( @@ -196,7 +166,7 @@ const OsqueryResponseActionParamsFormComponent = forwardRef< setQueryType={setQueryType} canRunPacks={canRunPacks} canRunSingleQuery={canRunSingleQuery} - resetFormFields={resetFormFields} + resetFormFields={reset} /> {queryType === 'query' && } diff --git a/x-pack/plugins/profiling/common/callee.test.ts b/x-pack/plugins/profiling/common/callee.test.ts index 0ae26e6d848e..4e3ef4b286e3 100644 --- a/x-pack/plugins/profiling/common/callee.test.ts +++ b/x-pack/plugins/profiling/common/callee.test.ts @@ -10,15 +10,28 @@ import { createCalleeTree } from './callee'; import { events, stackTraces, stackFrames, executables } from './__fixtures__/stacktraces'; +const totalSamples = sum([...events.values()]); +const totalFrames = sum([...stackTraces.values()].map((trace) => 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..f9d882e97c28 100644 --- a/x-pack/plugins/profiling/common/profiling.ts +++ b/x-pack/plugins/profiling/common/profiling.ts @@ -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..ba33c196b453 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,7 +9,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, - EuiLoadingSpinner, EuiPanel, EuiText, EuiTitle, @@ -17,7 +16,6 @@ import { 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'; interface Props { @@ -31,7 +29,6 @@ interface Props { totalSamples: number; totalSeconds: number; onClose: () => void; - status: AsyncStatus; } function KeyValueList({ rows }: { rows: Array<{ label: string; value: React.ReactNode }> }) { @@ -61,11 +58,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 +78,6 @@ function FlamegraphFrameInformationPanel({ - {status === AsyncStatus.Loading ? ( - - - - ) : undefined} @@ -101,16 +91,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', @@ -130,7 +114,7 @@ export function FlamegraphInformationWindow({ }); return ( - + = ({ }) => { const theme = useEuiTheme(); - const { - services: { fetchFrameInformation }, - } = useProfilingDependencies(); - const columnarData = useMemo(() => { return getFlamegraphModel({ primaryFlamegraph, @@ -193,38 +187,13 @@ 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], + exeFileName: primaryFlamegraph.ExeFilename[highlightedVmIndex], + sourceFileName: primaryFlamegraph.SourceFilename[highlightedVmIndex], + functionName: primaryFlamegraph.FunctionName[highlightedVmIndex], + countInclusive: primaryFlamegraph.CountInclusive[highlightedVmIndex], countExclusive: primaryFlamegraph.CountExclusive[highlightedVmIndex], } : undefined; @@ -271,7 +240,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 +256,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 +278,6 @@ export const FlameGraph: React.FC = ({ { diff --git a/x-pack/plugins/profiling/public/services.ts b/x-pack/plugins/profiling/public/services.ts index 07234ca124b3..a1f345dc96a2 100644 --- a/x-pack/plugins/profiling/public/services.ts +++ b/x-pack/plugins/profiling/public/services.ts @@ -7,9 +7,8 @@ import { CoreStart, 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'; export interface Services { @@ -31,10 +30,6 @@ export interface Services { timeTo: number; kuery: string; }) => Promise; - fetchFrameInformation: (params: { - frameID: string; - executableID: string; - }) => Promise; } export function getServices(core: CoreStart): Services { @@ -96,24 +91,8 @@ export function getServices(core: CoreStart): Services { 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: BaseFlameGraph = await core.http.get(paths.Flamechart, { query }); + 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..772d50581748 100644 --- a/x-pack/plugins/profiling/server/routes/flamechart.ts +++ b/x-pack/plugins/profiling/server/routes/flamechart.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; import { createCalleeTree } from '../../common/callee'; -import { createFlameGraph } from '../../common/flamegraph'; +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 +42,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 +61,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; 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/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/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 */} +