Repository: elastic/kibana
Pull request #218935: [APM] Improve environment combobox auto-complete
Author: login: crespocarlos
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: Team:obs-ux-infra_services - DEPRECATED
description: DEPRECATED - Use Team:obs-presentation.
name: v9.1.0
name: v8.19.0
Created: 2025-04-23T09:56:37Z | Merged: 2025-04-24T15:40:29Z

fixes https://github.com/elastic/kibana/issues/216974
## Summary

Improve the environment combobox typing behaviour

![env_dropdown](https://github.com/user-attachments/assets/f545f43a-e04e-4a22-b48b-ea68e277af60)



### How to test

- Navigate to Service Inventory
- Interact with the Environment dropdown
  - It will fetch from the server when: backspace is hit or when he text typed can't be matched with any items from the options list





Diff:
diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.test.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.test.tsx
new file mode 100644
index 0000000000000..69753e39c3d47
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.test.tsx
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+import { FETCH_STATUS } from '../../../hooks/use_fetcher';
+import { EnvironmentSelect } from '.';
+import { act, cleanup, fireEvent, render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+const DEFAULT_ENVIRONMENT = 'production';
+
+const mockOnSearchChange = jest.fn();
+jest.mock('./use_environment_select', () => ({
+  useEnvironmentSelect: jest.fn(() => ({
+    data: { terms: [] },
+    searchStatus: 'success',
+    onSearchChange: mockOnSearchChange,
+  })),
+}));
+
+describe('EnvironmentSelect', () => {
+  async function clearInputValue(input: HTMLInputElement) {
+    input.setSelectionRange(0, input.value.length);
+    await act(async () =>
+      userEvent.type(input, '{backspace}', {
+        initialSelectionEnd: 0,
+      })
+    );
+    fireEvent.input(input, { target: { value: '' } });
+  }
+
+  const defaultProps = {
+    environment: DEFAULT_ENVIRONMENT,
+    availableEnvironments: ['production', 'staging', 'development'],
+    status: FETCH_STATUS.SUCCESS,
+    serviceName: 'test-service',
+    rangeFrom: 'now-15m',
+    rangeTo: 'now',
+    onChange: jest.fn(),
+  };
+
+  afterEach(() => {
+    jest.clearAllMocks();
+    cleanup();
+  });
+
+  it('resets to the current environment on blur if no valid selection is made', async () => {
+    const { getByRole } = render(<EnvironmentSelect {...defaultProps} />);
+    const combobox = getByRole('combobox') as HTMLInputElement;
+
+    await clearInputValue(combobox);
+
+    expect(combobox).toHaveValue('');
+
+    act(() => {
+      fireEvent.blur(combobox);
+    });
+
+    expect(combobox).toHaveValue('production');
+  });
+
+  it('Should not call onSearchChange if item is already listed', async () => {
+    const { getByRole } = render(<EnvironmentSelect {...defaultProps} />);
+    const combobox = getByRole('combobox') as HTMLInputElement;
+
+    expect(mockOnSearchChange.mock.calls.length).toBe(0);
+
+    await clearInputValue(combobox);
+
+    expect(combobox).toHaveValue('');
+
+    expect(mockOnSearchChange.mock.calls.length).toBe(1);
+
+    act(() => {
+      fireEvent.input(combobox, { target: { value: 'dev' } });
+    });
+
+    expect(combobox).toHaveValue('dev');
+    expect(screen.getByText('development')).toBeInTheDocument();
+    expect(mockOnSearchChange.mock.calls.length).toBe(1);
+  });
+});
diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.tsx
index 4b19036776650..ac50f75c69247 100644
--- a/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.tsx
+++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.tsx
@@ -4,10 +4,9 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
-import { isEmpty } from 'lodash';
+
 import { i18n } from '@kbn/i18n';
-import React, { useMemo, useState } from 'react';
-import { debounce } from 'lodash';
+import React, { useCallback, useMemo, useState } from 'react';
 import type { EuiComboBoxOptionOption } from '@elastic/eui';
 import { EuiComboBox, EuiFormRow } from '@elastic/eui';
 import {
@@ -15,10 +14,10 @@ import {
   ENVIRONMENT_NOT_DEFINED,
   ENVIRONMENT_ALL,
 } from '../../../../common/environment_filter_values';
-import { SERVICE_ENVIRONMENT } from '../../../../common/es_fields/apm';
-import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
+import { FETCH_STATUS } from '../../../hooks/use_fetcher';
 import { useTimeRange } from '../../../hooks/use_time_range';
 import type { Environment } from '../../../../common/environment_rt';
+import { useEnvironmentSelect } from './use_environment_select';
 
 function getEnvironmentOptions(environments: Environment[]) {
   const environmentOptions = environments
@@ -35,6 +34,22 @@ function getEnvironmentOptions(environments: Environment[]) {
   ];
 }
 
+function shouldFetch({
+  newValue,
+  oldValue,
+  optionList,
+}: {
+  newValue: string;
+  oldValue: string;
+  optionList: string[];
+}) {
+  return (
+    newValue !== '' &&
+    (!optionList.some((option) => option.toLowerCase().includes(newValue.toLowerCase())) ||
+      !newValue.toLowerCase().includes(oldValue.toLowerCase()))
+  );
+}
+
 export function EnvironmentSelect({
   environment,
   availableEnvironments,
@@ -53,60 +68,80 @@ export function EnvironmentSelect({
   onChange: (value: string) => void;
 }) {
   const [searchValue, setSearchValue] = useState('');
-
-  const { start, end } = useTimeRange({ rangeFrom, rangeTo });
-
-  const selectedOptions: Array<EuiComboBoxOptionOption<string>> = [
+  const [selectedOption, setSelectedOption] = useState<Array<EuiComboBoxOptionOption<string>>>([
     {
       value: environment,
       label: getEnvironmentLabel(environment),
     },
-  ];
+  ]);
+
+  const { start, end } = useTimeRange({ rangeFrom, rangeTo });
+  const { data, onSearchChange, searchStatus } = useEnvironmentSelect({ serviceName, start, end });
 
   const onSelect = (changedOptions: Array<EuiComboBoxOptionOption<string>>) => {
     if (changedOptions.length === 1 && changedOptions[0].value) {
       onChange(changedOptions[0].value);
     }
+    setSelectedOption(changedOptions);
   };
 
-  const { data, status: searchStatus } = useFetcher(
-    (callApmApi) => {
-      return isEmpty(searchValue)
-        ? Promise.resolve({ terms: [] })
-        : callApmApi('GET /internal/apm/suggestions', {
-            params: {
-              query: {
-                fieldName: SERVICE_ENVIRONMENT,
-                fieldValue: searchValue,
-                serviceName,
-                start,
-                end,
-              },
-            },
-          });
+  const terms = useMemo(() => {
+    if (searchValue.trim() === '') {
+      return availableEnvironments;
+    }
+
+    return [...new Set(data?.terms.concat(...availableEnvironments))];
+  }, [availableEnvironments, data?.terms, searchValue]);
+
+  const options = useMemo<Array<EuiComboBoxOptionOption<string>>>(() => {
+    const environmentOptions = getEnvironmentOptions(terms);
+
+    return searchValue.trim() === ''
+      ? environmentOptions
+      : environmentOptions.filter((term) =>
+          term.value.toLowerCase().includes(searchValue.toLowerCase())
+        );
+  }, [terms, searchValue]);
+
+  const isInvalid = options.length === 0 && searchValue !== '';
+
+  const onSearch = useCallback(
+    (value: string) => {
+      setSearchValue(value);
+      if (
+        shouldFetch({
+          newValue: value,
+          oldValue: searchValue,
+          optionList: terms,
+        })
+      ) {
+        onSearchChange(value);
+      }
     },
-    [searchValue, start, end, serviceName]
+    [onSearchChange, terms, searchValue]
   );
-  const terms = data?.terms ?? [];
-  const isInvalid = terms.length === 0 && searchValue !== '';
-
-  const options: Array<EuiComboBoxOptionOption<string>> = [
-    ...(searchValue === ''
-      ? getEnvironmentOptions(availableEnvironments)
-      : terms.map((name) => {
-          return { label: name, value: name };
-        })),
-  ];
 
-  const onSearch = useMemo(() => debounce(setSearchValue, 300), []);
+  // in case the combobox is left empty, returns the current selected environment stored in the URL state
+  const onBlur = useCallback(() => {
+    setSelectedOption([
+      {
+        value: environment,
+        label: getEnvironmentLabel(environment),
+      },
+    ]);
+  }, [environment]);
 
   return (
     <EuiFormRow
+      aria-label={i18n.translate(
+        'xpack.apm.environmentSelect.selectenvironmentComboBox.ariaLabel',
+        { defaultMessage: 'Select environment' }
+      )}
       error={i18n.translate('xpack.apm.filter.environment.error', {
         defaultMessage: '{value} is not a valid environment',
         values: { value: searchValue },
       })}
-      style={{ minWidth: '256px' }}
+      css={{ minWidth: '256px' }}
       isInvalid={isInvalid}
     >
       <EuiComboBox
@@ -122,9 +157,10 @@ export function EnvironmentSelect({
         })}
         singleSelection={{ asPlainText: true }}
         options={options}
-        selectedOptions={selectedOptions}
-        onChange={(changedOptions) => onSelect(changedOptions)}
+        selectedOptions={selectedOption}
+        onChange={onSelect}
         onSearchChange={onSearch}
+        onBlur={onBlur}
         isLoading={status === FETCH_STATUS.LOADING || searchStatus === FETCH_STATUS.LOADING}
       />
     </EuiFormRow>
diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/use_environment_select.ts b/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/use_environment_select.ts
new file mode 100644
index 0000000000000..81e6492f88ae7
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/use_environment_select.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { SERVICE_ENVIRONMENT } from '@kbn/apm-types';
+import { useStateDebounced } from '../../../hooks/use_debounce';
+import { useFetcher } from '../../../hooks/use_fetcher';
+
+export function useEnvironmentSelect({
+  serviceName,
+  start,
+  end,
+}: {
+  serviceName?: string;
+  start: string;
+  end: string;
+}) {
+  const [debouncedSearchValue, setDebouncedSearchValue] = useStateDebounced('');
+
+  const { data, status: searchStatus } = useFetcher(
+    (callApmApi) => {
+      return debouncedSearchValue.trim() === ''
+        ? Promise.resolve({ terms: [] })
+        : callApmApi('GET /internal/apm/suggestions', {
+            params: {
+              query: {
+                fieldName: SERVICE_ENVIRONMENT,
+                fieldValue: debouncedSearchValue,
+                serviceName,
+                start,
+                end,
+              },
+            },
+          });
+    },
+    [debouncedSearchValue, start, end, serviceName]
+  );
+
+  const onSearchChange = (value: string) => {
+    setDebouncedSearchValue(value);
+  };
+
+  return { data, searchStatus, onSearchChange };
+}


Reviews:
user: login: jennypavlova
body: Tested on serverless and LGTM 🚀 
I noticed that the drop-down value is not removed on click before/after typing- that's expected, right? (I see on the PR video that it's removed, or it was using "select all" to remove it 🤔 )?

https://github.com/user-attachments/assets/d4b4d687-544b-4ea7-9408-40de6fe702c2



➕  Some nits ⬇️ 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T11:09:30Z
user: login: crespocarlos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T11:21:14Z
user: login: jennypavlova
body: >> I noticed that the drop-down value is not removed on click before/after typing- that's expected, right? (I see on the PR video that it's removed, or it was using "select all" to remove it 🤔 )?
> 
> Hey Jenny. I'm not sure if I understood the question

I saw on your screen recording that when you click on the combo-box, the value is cleared and you can start typing without anything in the input ( so if you have env "test" selected it will be removed on click) - or at least based on what I saw there, I wasn't sure if the environment was removed beforehand 😅  In my case when I click on the combobox the value was not cleared and I was typing on the already existing environment value in the input ( so if "test" was the previous value it will be still there when I start typing ). 
I am not sure which one is the better experience in this case, it looked different from what I saw in your screen recording, that's why I asked.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T12:24:07Z

Review comments:
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { SERVICE_ENVIRONMENT } from '@kbn/apm-types';
+import { isEmpty } from 'lodash';
+import { useStateDebounced } from '../../../hooks/use_debounce';
+import { useFetcher } from '../../../hooks/use_fetcher';
+
+export function useEnvironmentSelect({
+  serviceName,
+  start,
+  end,
+}: {
+  serviceName?: string;
+  start: string;
+  end: string;
+}) {
+  const [debouncedSearchValue, setDebouncedSearchValue] = useStateDebounced('');
+
+  const { data, status: searchStatus } = useFetcher(
+    (callApmApi) => {
+      return isEmpty(debouncedSearchValue)
path: x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/use_environment_select.ts
user: login: jennypavlova
body: Nit: Do we really need `isEmpty` here? Could we just check for `truthy` value: 
```suggestion
      return debouncedSearchValue
```

Or `debouncedSearchValue !== ''`
created_at: 2025-04-24T10:55:05Z
author_association: MEMBER
diff_hunk: @@ -53,60 +68,80 @@ export function EnvironmentSelect({
   onChange: (value: string) => void;
 }) {
   const [searchValue, setSearchValue] = useState('');
-
-  const { start, end } = useTimeRange({ rangeFrom, rangeTo });
-
-  const selectedOptions: Array<EuiComboBoxOptionOption<string>> = [
+  const [selectedOption, setSelectedOption] = useState<Array<EuiComboBoxOptionOption<string>>>([
     {
       value: environment,
       label: getEnvironmentLabel(environment),
     },
-  ];
+  ]);
+
+  const { start, end } = useTimeRange({ rangeFrom, rangeTo });
+  const { data, onSearchChange, searchStatus } = useEnvironmentSelect({ serviceName, start, end });
 
   const onSelect = (changedOptions: Array<EuiComboBoxOptionOption<string>>) => {
     if (changedOptions.length === 1 && changedOptions[0].value) {
       onChange(changedOptions[0].value);
     }
+    setSelectedOption(changedOptions);
   };
 
-  const { data, status: searchStatus } = useFetcher(
-    (callApmApi) => {
-      return isEmpty(searchValue)
-        ? Promise.resolve({ terms: [] })
-        : callApmApi('GET /internal/apm/suggestions', {
-            params: {
-              query: {
-                fieldName: SERVICE_ENVIRONMENT,
-                fieldValue: searchValue,
-                serviceName,
-                start,
-                end,
-              },
-            },
-          });
+  const terms = useMemo(() => {
+    if (isEmpty(searchValue)) {
+      return availableEnvironments;
+    }
+
+    return [...new Set(data?.terms.concat(...availableEnvironments))];
+  }, [availableEnvironments, data?.terms, searchValue]);
+
+  const options = useMemo<Array<EuiComboBoxOptionOption<string>>>(() => {
+    const environmentOptions = getEnvironmentOptions(terms);
+
+    return isEmpty(searchValue)
path: x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.tsx
user: login: jennypavlova
body: Nit I saw that in `isInvalid` on line 106 we use `searchValue !== ''` can we do the same or just check `searchValue` and remove the `isEmpty` - also in the other places and we will have it consistent in the file + reduce the dependencies 😎 
```suggestion
    return searchValue !== ''
```
created_at: 2025-04-24T11:00:10Z
author_association: MEMBER
diff_hunk: @@ -53,60 +68,80 @@ export function EnvironmentSelect({
   onChange: (value: string) => void;
 }) {
   const [searchValue, setSearchValue] = useState('');
-
-  const { start, end } = useTimeRange({ rangeFrom, rangeTo });
-
-  const selectedOptions: Array<EuiComboBoxOptionOption<string>> = [
+  const [selectedOption, setSelectedOption] = useState<Array<EuiComboBoxOptionOption<string>>>([
     {
       value: environment,
       label: getEnvironmentLabel(environment),
     },
-  ];
+  ]);
+
+  const { start, end } = useTimeRange({ rangeFrom, rangeTo });
+  const { data, onSearchChange, searchStatus } = useEnvironmentSelect({ serviceName, start, end });
 
   const onSelect = (changedOptions: Array<EuiComboBoxOptionOption<string>>) => {
     if (changedOptions.length === 1 && changedOptions[0].value) {
       onChange(changedOptions[0].value);
     }
+    setSelectedOption(changedOptions);
   };
 
-  const { data, status: searchStatus } = useFetcher(
-    (callApmApi) => {
-      return isEmpty(searchValue)
-        ? Promise.resolve({ terms: [] })
-        : callApmApi('GET /internal/apm/suggestions', {
-            params: {
-              query: {
-                fieldName: SERVICE_ENVIRONMENT,
-                fieldValue: searchValue,
-                serviceName,
-                start,
-                end,
-              },
-            },
-          });
+  const terms = useMemo(() => {
+    if (isEmpty(searchValue)) {
path: x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.tsx
user: login: jennypavlova
body: Nit: same
```suggestion
    if (searchValue !== '')) {
```
or just `searchValue`
created_at: 2025-04-24T11:00:50Z
author_association: MEMBER
diff_hunk: @@ -6,19 +6,18 @@
  */
 import { isEmpty } from 'lodash';
path: x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.tsx
user: login: jennypavlova
body: Nit: then we can remove it:
```suggestion
```
created_at: 2025-04-24T11:01:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { SERVICE_ENVIRONMENT } from '@kbn/apm-types';
+import { isEmpty } from 'lodash';
+import { useStateDebounced } from '../../../hooks/use_debounce';
+import { useFetcher } from '../../../hooks/use_fetcher';
+
+export function useEnvironmentSelect({
+  serviceName,
+  start,
+  end,
+}: {
+  serviceName?: string;
+  start: string;
+  end: string;
+}) {
+  const [debouncedSearchValue, setDebouncedSearchValue] = useStateDebounced('');
+
+  const { data, status: searchStatus } = useFetcher(
+    (callApmApi) => {
+      return isEmpty(debouncedSearchValue)
path: x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/use_environment_select.ts
user: login: crespocarlos
body: yeah. just used what was there and didn't think about removing lodash. I'll do that. thanks
created_at: 2025-04-24T11:21:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
path: x-pack/solutions/observability/plugins/apm/public/components/shared/environment_select/index.test.tsx
user: login: jennypavlova
body: Nit: extra line 
```suggestion
```
created_at: 2025-04-24T12:22:34Z
author_association: MEMBER

PR comments:
user: login: crespocarlos
created_at: 2025-04-23T12:52:06Z
body: @elasticmachine merge upstream
author_association: CONTRIBUTOR
user: login: elasticmachine
created_at: 2025-04-24T07:52:19Z
body: Pinging @elastic/obs-ux-infra_services-team (Team:obs-ux-infra_services)
author_association: CONTRIBUTOR
user: login: crespocarlos
created_at: 2025-04-24T11:59:08Z
body: > I noticed that the drop-down value is not removed on click before/after typing- that's expected, right? (I see on the PR video that it's removed, or it was using "select all" to remove it 🤔 )?

Hey Jenny. I'm not sure if I understood the question
author_association: CONTRIBUTOR
user: login: crespocarlos
created_at: 2025-04-24T13:44:37Z
body: > I saw on your screen recording that when you click on the combo-box, the value is cleared and you can start typing without anything in the input ( so if you have env "test" selected it will be removed on click) - or at least based on what I saw there, I wasn't sure if the environment was removed beforehand 😅 In my case when I click on the combobox the value was not cleared and I was typing on the already existing environment value in the input ( so if "test" was the previous value it will be still there when I start typing ).
> I am not sure which one is the better experience in this case, it looked different from what I saw in your screen recording, that's why I asked.

Got it. Yeah. I selected all and removed, I think it's ok to keep it like this for now. Let's see how this goes
author_association: CONTRIBUTOR
user: login: jennypavlova
created_at: 2025-04-24T13:45:36Z
body: > Got it. Yeah. I selected all and removed, I think it's ok to keep it like this for now. Let's see how this goes

Sure 👍 Thanks for confirming and for all the fixes 💯 
author_association: MEMBER
user: login: elasticmachine
created_at: 2025-04-24T15:41:10Z
body: ## :green_heart: Build Succeeded
* [Buildkite Build](https://buildkite.com/elastic/kibana-pull-request/builds/295823)
* Commit: 8bbb5e7caa5a78e7a9cf878d4ef29dbd1b4414d0

### Metrics [[docs](https://www.elastic.co/guide/en/kibana/master/ci-metrics.html)]
#### Module Count
> Fewer modules leads to a faster build time


| id | [before](https://github.com/elastic/kibana/commit/37afbb56d807a75d148fc509be82c140be8cb0c8) | [after](https://github.com/elastic/kibana/commit/8bbb5e7caa5a78e7a9cf878d4ef29dbd1b4414d0) | diff |
| --- | --- | --- | --- |
| `apm` | 1883 | 1884 | +1 |

#### 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/37afbb56d807a75d148fc509be82c140be8cb0c8) | [after](https://github.com/elastic/kibana/commit/8bbb5e7caa5a78e7a9cf878d4ef29dbd1b4414d0) | diff |
| --- | --- | --- | --- |
| `apm` | 2.6MB | 2.6MB | +774.0B |



### History
* :yellow_heart: [Build #295735](https://buildkite.com/elastic/kibana-pull-request/builds/295735) was flaky 6d7737d93a269983f206544def07a319ceb84c00
* :broken_heart: [Build #295270](https://buildkite.com/elastic/kibana-pull-request/builds/295270) failed 5b41502d6083703e4de484b485412a8f1d358193

<!--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/295823","number":295823,"commit":"8bbb5e7caa5a78e7a9cf878d4ef29dbd1b4414d0"},{"buildStatus":{"state":"passed","success":true,"hasRetries":true,"hasNonPreemptionRetries":true},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/295735","number":295735,"commit":"6d7737d93a269983f206544def07a319ceb84c00"},{"buildStatus":{"state":"failed","success":false,"hasRetries":true,"hasNonPreemptionRetries":true},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/295270","number":295270,"commit":"5b41502d6083703e4de484b485412a8f1d358193"}],"number":295823}
buildkite-pr-comment-->
author_association: CONTRIBUTOR
user: login: crespocarlos
created_at: 2025-05-02T15:33:08Z
body: ## 💚 All backports created successfully

| Status | Branch | Result |
|:------:|:------:|:------|
|✅|8.19|[<img src="https://img.shields.io/github/pulls/detail/state/elastic/kibana/219976">](https://github.com/elastic/kibana/pull/219976)|

Note: Successful backport PRs will be merged automatically after passing CI.

### Questions ?
Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport)

<!--- Backport version: 9.6.6 -->
author_association: CONTRIBUTOR