Repository: elastic/kibana
Pull request #213261: [Onboarding][OTel K8s] Use MOTel managed service on Serverless
Author: login: mykolaharmash
Labels: name: release_note:skip
description: Skip the PR/issue when compiling release notes
name: backport:skip
description: This PR does not require backporting
name: ci:project-deploy-observability
description: Create an Observability project
name: Feature: Observability Onboarding
name: v9.1.0
Created: 2025-03-05T15:57:51Z | Merged: 2025-03-19T09:58:40Z

Closes https://github.com/elastic/kibana/issues/208037

This change switches OTel K8S quickstart flow on Serverless to the managed OTel collector as the ingest endpoint.

* Adds shared `useOtelIngestEndpointUrl` hook to be later re-used in OTel Host flow as well
* Adds the logic to use APM API key on serverless to access the managed service endpoint
* Modifies the code snipped with the new variables

## How to test

* Use the Serverless instance deployed from this PR, make sure OTel K8S flow code snippet uses the managed service endpoint, ingest logs from your computer (you can use [reference-stack cluster](https://github.com/elastic/oblt-reference-stack) with minikube)
* Run the classic Kibana locally, and make sure the OTel K8S flow uses the usual code snippet with ES ingest endpoint, ingest logs from your computer



Diff:
diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/kibana.jsonc b/x-pack/solutions/observability/plugins/observability_onboarding/kibana.jsonc
index 361451b1ce009..71a81d19a3bbb 100644
--- a/x-pack/solutions/observability/plugins/observability_onboarding/kibana.jsonc
+++ b/x-pack/solutions/observability/plugins/observability_onboarding/kibana.jsonc
@@ -22,7 +22,8 @@
       "discover",
       "share",
       "fleet",
-      "customIntegrations"
+      "customIntegrations",
+      "apm"
     ],
     "optionalPlugins": [
       "cloud",
@@ -35,4 +36,4 @@
       "common"
     ]
   }
-}
\ No newline at end of file
+}
diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx
index 0b06157c9b393..3bce2695b47a1 100644
--- a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx
+++ b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import React, { useEffect, useState } from 'react';
+import React, { useState, useEffect } from 'react';
 import {
   EuiPanel,
   EuiSkeletonText,
@@ -27,11 +27,11 @@ import { FormattedMessage } from '@kbn/i18n-react';
 import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
 import { css } from '@emotion/react';
 import { usePerformanceContext } from '@kbn/ebt-tools';
+import { type ObservabilityOnboardingAppServices } from '../../..';
 import { EmptyPrompt } from '../shared/empty_prompt';
 import { GetStartedPanel } from '../shared/get_started_panel';
 import { FeedbackButtons } from '../shared/feedback_buttons';
 import { CopyToClipboardButton } from '../shared/copy_to_clipboard_button';
-import { ObservabilityOnboardingContextValue } from '../../../plugin';
 import { useKubernetesFlow } from '../kubernetes/use_kubernetes_flow';
 
 const OTEL_HELM_CHARTS_REPO = 'https://open-telemetry.github.io/opentelemetry-helm-charts';
@@ -42,8 +42,11 @@ export const OtelKubernetesPanel: React.FC = () => {
   const { data, error, refetch } = useKubernetesFlow('kubernetes_otel');
   const [idSelected, setIdSelected] = useState('nodejs');
   const {
-    services: { share },
-  } = useKibana<ObservabilityOnboardingContextValue>();
+    services: {
+      share,
+      context: { isServerless },
+    },
+  } = useKibana<ObservabilityOnboardingAppServices>();
   const apmLocator = share.url.locators.get('APM_LOCATOR');
   const dashboardLocator = share.url.locators.get(DASHBOARD_APP_LOCATOR);
   const theme = useEuiTheme();
@@ -59,14 +62,19 @@ export const OtelKubernetesPanel: React.FC = () => {
     }
   }, [data, onPageReady]);
 
+  const ingestEndpointUrl = isServerless ? data?.managedOtlpServiceUrl : data?.elasticsearchUrl;
+
   if (error) {
     return (
       <EmptyPrompt onboardingFlowType="kubernetes_otel" error={error} onRetryClick={refetch} />
     );
   }
 
+  const elasticEndpointVarName = isServerless ? 'elastic_otlp_endpoint' : 'elastic_endpoint';
+  const valuesFileSubfolder = isServerless ? '/managed_otlp' : '';
+
   const otelKubeStackValuesFileUrl = data
-    ? `https://raw.githubusercontent.com/elastic/elastic-agent/refs/tags/v${data.elasticAgentVersionInfo.agentBaseVersion}/deploy/helm/edot-collector/kube-stack/values.yaml`
+    ? `https://raw.githubusercontent.com/elastic/elastic-agent/refs/tags/v${data.elasticAgentVersionInfo.agentBaseVersion}/deploy/helm/edot-collector/kube-stack${valuesFileSubfolder}/values.yaml`
     : '';
   const namespace = 'opentelemetry-operator-system';
   const addRepoCommand = `helm repo add open-telemetry '${OTEL_HELM_CHARTS_REPO}' --force-update`;
@@ -74,7 +82,7 @@ export const OtelKubernetesPanel: React.FC = () => {
     ? `kubectl create namespace ${namespace}
 kubectl create secret generic elastic-secret-otel \\
   --namespace ${namespace} \\
-  --from-literal=elastic_endpoint='${data.elasticsearchUrl}' \\
+  --from-literal=${elasticEndpointVarName}='${ingestEndpointUrl}' \\
   --from-literal=elastic_api_key='${data.apiKeyEncoded}'
 helm install opentelemetry-kube-stack open-telemetry/opentelemetry-kube-stack \\
   --namespace ${namespace} \\
diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/api_key/create_managed_otlp_service_api_key.ts b/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/api_key/create_managed_otlp_service_api_key.ts
new file mode 100644
index 0000000000000..8a2ec264b277a
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/api_key/create_managed_otlp_service_api_key.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { type ElasticsearchClient } from '@kbn/core/server';
+
+export function createManagedOtlpServiceApiKey(esClient: ElasticsearchClient, name: string) {
+  return esClient.security.createApiKey({
+    name,
+    role_descriptors: {
+      otel_managed_service: {
+        cluster: [],
+        index: [],
+        applications: [
+          {
+            application: 'apm',
+            privileges: ['event:write'],
+            resources: ['*'],
+          },
+        ],
+      },
+    },
+  });
+}
diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/get_managed_otlp_service_url.test.ts b/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/get_managed_otlp_service_url.test.ts
new file mode 100644
index 0000000000000..0b8325f09fe4f
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/get_managed_otlp_service_url.test.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { of } from 'rxjs';
+import type { ObservabilityOnboardingRouteHandlerResources } from '../routes/types';
+import { getManagedOtlpServiceUrl } from './get_managed_otlp_service_url';
+
+describe('getManagedOtlpServiceUrl()', () => {
+  it('returns empty string if APM managed service URL is not set', async () => {
+    const plugins = {
+      apm: {
+        setup: {
+          config$: of({
+            managedServiceUrl: '',
+          }),
+        },
+      },
+    };
+    const result = await getManagedOtlpServiceUrl({
+      plugins,
+    } as unknown as ObservabilityOnboardingRouteHandlerResources);
+
+    expect(result).toBe('');
+  });
+
+  it('returns managed OTLP service URL endpoint by converting APM managed service URL and adding port number', async () => {
+    const plugins = {
+      apm: {
+        setup: {
+          config$: of({
+            managedServiceUrl: 'https://e2e-tests-c045db.apm.us-east-1.aws.elastic.cloud',
+          }),
+        },
+      },
+    };
+    const result = await getManagedOtlpServiceUrl({
+      plugins,
+    } as unknown as ObservabilityOnboardingRouteHandlerResources);
+
+    expect(result).toBe('https://e2e-tests-c045db.ingest.us-east-1.aws.elastic.cloud:443');
+  });
+});
diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/get_managed_otlp_service_url.ts b/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/get_managed_otlp_service_url.ts
new file mode 100644
index 0000000000000..06023290e0e4d
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/observability_onboarding/server/lib/get_managed_otlp_service_url.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 { firstValueFrom } from 'rxjs';
+import type { ObservabilityOnboardingRouteHandlerResources } from '../routes/types';
+
+/**
+ * Managed OTLP service endpoint is not exposed yet
+ * separately, so we need to construct it from the APM
+ * service endpoint by replacing .apm with .ingest.
+ * Once https://github.com/elastic/cloud/issues/137354 is
+ * implemented, we can get the URL from the Cloud plugin.
+ */
+export async function getManagedOtlpServiceUrl({
+  plugins,
+}: ObservabilityOnboardingRouteHandlerResources): Promise<string> {
+  const managedApmServiceUrl = await firstValueFrom(plugins.apm.setup.config$).then((apmConfig) => {
+    return apmConfig.managedServiceUrl;
+  });
+
+  if (!managedApmServiceUrl) {
+    return '';
+  }
+
+  const urlParts = managedApmServiceUrl.split('.');
+
+  return `${urlParts[0]}.ingest.${urlParts.slice(2).join('.')}:443`;
+}
diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/server/routes/kubernetes/route.ts b/x-pack/solutions/observability/plugins/observability_onboarding/server/routes/kubernetes/route.ts
index 87100e78175b8..4efbdf848dad6 100644
--- a/x-pack/solutions/observability/plugins/observability_onboarding/server/routes/kubernetes/route.ts
+++ b/x-pack/solutions/observability/plugins/observability_onboarding/server/routes/kubernetes/route.ts
@@ -16,12 +16,15 @@ import { createObservabilityOnboardingServerRoute } from '../create_observabilit
 import { hasLogMonitoringPrivileges } from '../../lib/api_key/has_log_monitoring_privileges';
 import { createShipperApiKey } from '../../lib/api_key/create_shipper_api_key';
 import { getAgentVersionInfo } from '../../lib/get_agent_version';
+import { createManagedOtlpServiceApiKey } from '../../lib/api_key/create_managed_otlp_service_api_key';
+import { getManagedOtlpServiceUrl } from '../../lib/get_managed_otlp_service_url';
 
 export interface CreateKubernetesOnboardingFlowRouteResponse {
   apiKeyEncoded: string;
   onboardingId: string;
   elasticsearchUrl: string;
   elasticAgentVersionInfo: ElasticAgentVersionInfo;
+  managedOtlpServiceUrl: string;
 }
 
 export interface HasKubernetesDataRouteResponse {
@@ -40,14 +43,8 @@ const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerR
         'Authorization is checked by custom logic using Elasticsearch client and by the Package Service client',
     },
   },
-  async handler({
-    context,
-    request,
-    params,
-    plugins,
-    services,
-    kibanaVersion,
-  }): Promise<CreateKubernetesOnboardingFlowRouteResponse> {
+  async handler(resources): Promise<CreateKubernetesOnboardingFlowRouteResponse> {
+    const { context, request, params, plugins, services, kibanaVersion, config } = resources;
     const {
       elasticsearch: { client },
     } = await context.core;
@@ -62,9 +59,16 @@ const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerR
 
     const fleetPluginStart = await plugins.fleet.start();
     const packageClient = fleetPluginStart.packageService.asScoped(request);
+    const apiKeyPromise =
+      config.serverless.enabled && params.body.pkgName === 'kubernetes_otel'
+        ? createManagedOtlpServiceApiKey(
+            client.asCurrentUser,
+            `ingest-otel-k8s-${new Date().toISOString()}`
+          )
+        : createShipperApiKey(client.asCurrentUser, `${params.body.pkgName}_onboarding`, true);
 
     const [{ encoded: apiKeyEncoded }, elasticAgentVersionInfo] = await Promise.all([
-      createShipperApiKey(client.asCurrentUser, `${params.body.pkgName}_onboarding`, true),
+      apiKeyPromise,
       getAgentVersionInfo(fleetPluginStart, kibanaVersion),
       // System package is always required
       packageClient.ensureInstalledPackage({ pkgName: 'system' }),
@@ -85,6 +89,7 @@ const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerR
       apiKeyEncoded,
       elasticsearchUrl: elasticsearchUrlList.length > 0 ? elasticsearchUrlList[0] : '',
       elasticAgentVersionInfo,
+      managedOtlpServiceUrl: await getManagedOtlpServiceUrl(resources),
     };
   },
 });
diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/server/types.ts b/x-pack/solutions/observability/plugins/observability_onboarding/server/types.ts
index 92f65cbe1b7d6..e6347cc07dd96 100644
--- a/x-pack/solutions/observability/plugins/observability_onboarding/server/types.ts
+++ b/x-pack/solutions/observability/plugins/observability_onboarding/server/types.ts
@@ -12,6 +12,7 @@ import {
   PluginStart as DataPluginStart,
 } from '@kbn/data-plugin/server';
 import { FleetSetupContract, FleetStartContract } from '@kbn/fleet-plugin/server';
+import { APMPluginSetup } from '@kbn/apm-plugin/server';
 import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server';
 import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
 import {
@@ -26,6 +27,7 @@ export interface ObservabilityOnboardingPluginSetupDependencies {
   usageCollection: UsageCollectionSetup;
   fleet: FleetSetupContract;
   customIntegrations: CustomIntegrationsPluginSetup;
+  apm: APMPluginSetup;
 }
 
 export interface ObservabilityOnboardingPluginStartDependencies {
@@ -35,6 +37,7 @@ export interface ObservabilityOnboardingPluginStartDependencies {
   usageCollection: undefined;
   fleet: FleetStartContract;
   customIntegrations: CustomIntegrationsPluginStart;
+  apm: APMPluginSetup;
 }
 
 // eslint-disable-next-line @typescript-eslint/no-empty-interface
diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/tsconfig.json b/x-pack/solutions/observability/plugins/observability_onboarding/tsconfig.json
index ce26619ab7f63..22ca69dc1d847 100644
--- a/x-pack/solutions/observability/plugins/observability_onboarding/tsconfig.json
+++ b/x-pack/solutions/observability/plugins/observability_onboarding/tsconfig.json
@@ -46,7 +46,8 @@
     "@kbn/core-plugins-server",
     "@kbn/logs-shared-plugin",
     "@kbn/tooling-log",
-    "@kbn/ebt-tools"
+    "@kbn/ebt-tools",
+    "@kbn/apm-plugin"
   ],
   "exclude": [
     "target/**/*"


Reviews:
user: login: flash1293
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T13:50:59Z
user: login: vigneshshanmugam
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-07T20:08:31Z
user: login: flash1293
body: Tested and works fine for me, someone from the ingest side should check whether the permissions are the right one, I don't have the necessary insight here.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T13:44:59Z

Review comments:
diff_hunk: @@ -26,49 +26,95 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
 import { FormattedMessage } from '@kbn/i18n-react';
 import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
 import { css } from '@emotion/react';
+import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
+import { APMApiKeyResponse } from '../../../../common/types';
+import { ObservabilityOnboardingAppServices } from '../../..';
 import { EmptyPrompt } from '../shared/empty_prompt';
 import { GetStartedPanel } from '../shared/get_started_panel';
 import { FeedbackButtons } from '../shared/feedback_buttons';
 import { CopyToClipboardButton } from '../shared/copy_to_clipboard_button';
-import { ObservabilityOnboardingContextValue } from '../../../plugin';
 import { useKubernetesFlow } from '../kubernetes/use_kubernetes_flow';
+import { useOtelIngestEndpointUrl } from '../shared/use_otel_ingest_endpoint_url';
 
 const OTEL_HELM_CHARTS_REPO = 'https://open-telemetry.github.io/opentelemetry-helm-charts';
 const OTEL_KUBE_STACK_VERSION = '0.3.9';
 const CLUSTER_OVERVIEW_DASHBOARD_ID = 'kubernetes_otel-cluster-overview';
 
 export const OtelKubernetesPanel: React.FC = () => {
-  const { data, error, refetch } = useKubernetesFlow('kubernetes_otel');
+  const { data, error: createFlowError, refetch } = useKubernetesFlow('kubernetes_otel');
+  const [error, setError] = useState<IHttpFetchError<ResponseErrorBody> | null>(null);
   const [idSelected, setIdSelected] = useState('nodejs');
+  const [encodedApiKey, setEncodedApiKey] = useState<string | null>(null);
   const {
-    services: { share },
-  } = useKibana<ObservabilityOnboardingContextValue>();
+    services: {
+      http,
+      share,
+      context: { isServerless },
+    },
+  } = useKibana<ObservabilityOnboardingAppServices>();
   const apmLocator = share.url.locators.get('APM_LOCATOR');
   const dashboardLocator = share.url.locators.get(DASHBOARD_APP_LOCATOR);
   const theme = useEuiTheme();
+  const ingestEndpointUrl = useOtelIngestEndpointUrl({
+    elasticsearchUrl: data?.elasticsearchUrl,
+    managedServiceUrl: data?.managedServiceUrl,
+  });
+
+  useEffect(() => {
+    if (createFlowError) {
+      setError(createFlowError);
+    }
+  }, [createFlowError]);
+
+  useEffect(() => {
+    if (encodedApiKey !== null || !data) {
+      return;
+    }
+
+    if (!isServerless) {
+      setEncodedApiKey(data.apiKeyEncoded);
+      return;
+    }
+
+    const timestamp = new Date().toISOString();
+
+    http
+      .post<APMApiKeyResponse>('/api/apm/agent_keys', {
path: x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx
user: login: flash1293
body: similar to the other PR, we should use our own endpoint with limited privileges 
created_at: 2025-03-07T13:50:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { type ElasticsearchClient } from '@kbn/core/server';
+
+export function createManagedServiceApiKey(esClient: ElasticsearchClient, name: string) {
path: x-pack/solutions/observability/plugins/observability_onboarding/server/lib/api_key/create_managed_service_api_key.ts
user: login: vigneshshanmugam
body: nit: to keep the naming consistent, lets use Managed OTLP 
created_at: 2025-03-07T20:08:31Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-06T16:03:27Z
body: 
### :robot: GitHub comments

<details><summary>Expand to view the GitHub comments</summary>
<p>

Just comment with:
- `/oblt-deploy` : Deploy a Kibana instance using the Observability test environments.
- `run` `docs-build` : Re-trigger the docs validation. (use unformatted text in the comment!)
</p>
</details>

author_association: CONTRIBUTOR
user: login: mykolaharmash
created_at: 2025-03-11T10:27:25Z
body: I've re-tested the flow with the latest changes and everything works as expected, @flash1293 please take another look
author_association: CONTRIBUTOR
user: login: flash1293
created_at: 2025-03-11T13:45:52Z
body: Just one nit: The name of the generated API key is not consistent, should we use the same pattern for the different flows?
author_association: CONTRIBUTOR
user: login: mykolaharmash
created_at: 2025-03-12T14:41:10Z
body: Tested the flow using [the e2e testing steps](https://github.com/elastic/opentelemetry-dev/issues/557) which includes traces, everything works as expected.

| Dashboard | Discover | APM |
| --- | --- | --- |
| ![CleanShot 2025-03-12 at 15 34 17@2x](https://github.com/user-attachments/assets/6726000b-1f66-4ab8-80c4-0b16fd763c69) | ![CleanShot 2025-03-12 at 15 35 48@2x](https://github.com/user-attachments/assets/6fbd63f4-738d-4637-8b14-21f8bc6a20bd) | ![CleanShot 2025-03-12 at 15 37 03@2x](https://github.com/user-attachments/assets/c3cefacf-ef16-44ea-8ae1-9531519e2d54) |

author_association: CONTRIBUTOR
user: login: mykolaharmash
created_at: 2025-03-12T15:38:43Z
body: > Just one nit: The name of the generated API key is not consistent, should we use the same pattern for the different flows?

Created a [separate issue](https://github.com/elastic/kibana/issues/214204) for unifying the key names.
author_association: CONTRIBUTOR
user: login: elasticmachine
created_at: 2025-03-18T11:02:39Z
body: ## :green_heart: Build Succeeded
* [Buildkite Build](https://buildkite.com/elastic/kibana-pull-request/builds/284897)
* Commit: e0ae0023417ab40f606b982b7b509de64070d9e3
* Kibana Serverless Image: `docker.elastic.co/kibana-ci/kibana-serverless:pr-213261-e0ae0023417a`

### Metrics [[docs](https://www.elastic.co/guide/en/kibana/master/ci-metrics.html)]
#### Async chunks
> Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app


| id | [before](https://github.com/elastic/kibana/commit/0b9df094d1770065f79c05272a41175396caa27b) | [after](https://github.com/elastic/kibana/commit/e0ae0023417ab40f606b982b7b509de64070d9e3) | diff |
| --- | --- | --- | --- |
| `observabilityOnboarding` | 262.2KB | 262.3KB | +153.0B |



### History
* :green_heart: [Build #284647](https://buildkite.com/elastic/kibana-pull-request/builds/284647) succeeded eaaa7a350781a60bd9f1a0929c7551426e3bb8c2
* :broken_heart: [Build #284515](https://buildkite.com/elastic/kibana-pull-request/builds/284515) failed ee3d506d3f109094360ae64b03b6935dfd7ef243
* :broken_heart: [Build #284424](https://buildkite.com/elastic/kibana-pull-request/builds/284424) failed 69574bb325ed518dbb7814cedcaaf552c6e24330
* :green_heart: [Build #283683](https://buildkite.com/elastic/kibana-pull-request/builds/283683) succeeded 997d45d1b77a5501396ca1e656dbffee5d85daf5
* :green_heart: [Build #282341](https://buildkite.com/elastic/kibana-pull-request/builds/282341) succeeded 60c43620dc89e6958f5958be377dfecbebe998c4

<!--buildkite-pr-comment-kibana-pull-request
{"builds":[{"buildStatus":{"state":"passed","success":true,"hasRetries":true,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/284897","number":284897,"commit":"e0ae0023417ab40f606b982b7b509de64070d9e3"},{"buildStatus":{"state":"running","success":true,"hasRetries":true,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/284647","number":284647,"commit":"eaaa7a350781a60bd9f1a0929c7551426e3bb8c2"},{"buildStatus":{"state":"failed","success":false,"hasRetries":true,"hasNonPreemptionRetries":true},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/284515","number":284515,"commit":"ee3d506d3f109094360ae64b03b6935dfd7ef243"},{"buildStatus":{"state":"failed","success":false,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/284424","number":284424,"commit":"69574bb325ed518dbb7814cedcaaf552c6e24330"},{"buildStatus":{"state":"passed","success":true,"hasRetries":true,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/283683","number":283683,"commit":"997d45d1b77a5501396ca1e656dbffee5d85daf5"}],"number":284897}
buildkite-pr-comment-->
author_association: CONTRIBUTOR